diff --git a/app/build.gradle b/app/build.gradle index 366f98b87..6bc70aa1a 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -91,6 +91,9 @@ android { lint { abortOnError true } + buildFeatures { + viewBinding true + } } dependencies { @@ -107,6 +110,8 @@ dependencies { // color picker for user-defined colors implementation 'com.github.martin-stone:hsv-alpha-color-picker-android:3.1.0' + implementation 'androidx.activity:activity:1.9.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' // test testImplementation 'junit:junit:4.13.2' @@ -115,4 +120,14 @@ dependencies { testImplementation 'org.robolectric:robolectric:4.12.1' testImplementation 'androidx.test:runner:1.5.2' testImplementation 'androidx.test:core:1.5.0' + + // Generative AI + implementation("com.google.ai.client.generativeai:generativeai:0.9.0") + + //multiple bottom sheets + implementation ("com.roshaan.multiplebottomsheets:multiplebottomsheets:1.0.1") + implementation ("com.google.android.material:material:1.12.0") + // Rich Editor + + implementation("com.mohamedrejeb.richeditor:richeditor-compose:1.0.0-rc05") } diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml index e8755c20a..124bd90df 100644 --- a/app/src/debug/res/values/strings.xml +++ b/app/src/debug/res/values/strings.xml @@ -3,8 +3,8 @@ SPDX-License-Identifier: GPL-3.0-only --> - HeliBoard debug - HeliBoard debug Spell Checker - HeliBoard debug Settings + Oscar AI Keyboard + Oscar debug Spell Checker + Oscar debug Settings diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f0cee5671..86e800285 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,8 +15,6 @@ SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only @@ -99,7 +96,26 @@ SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only - + + + + diff --git a/app/src/main/java/helium314/keyboard/AIEngine/AIState.kt b/app/src/main/java/helium314/keyboard/AIEngine/AIState.kt new file mode 100644 index 000000000..65c517c64 --- /dev/null +++ b/app/src/main/java/helium314/keyboard/AIEngine/AIState.kt @@ -0,0 +1,9 @@ +package helium314.keyboard.AIEngine + +import com.mohamedrejeb.richeditor.model.RichTextState + +data class AIState( + val isAIProcessing: Boolean = false, + val isAICorrecting: Boolean = false, + val aiText: RichTextState = RichTextState(), +) diff --git a/app/src/main/java/helium314/keyboard/AIEngine/SummarizeUiState.kt b/app/src/main/java/helium314/keyboard/AIEngine/SummarizeUiState.kt new file mode 100644 index 000000000..2692afd49 --- /dev/null +++ b/app/src/main/java/helium314/keyboard/AIEngine/SummarizeUiState.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package helium314.keyboard.AIEngine + +/** + * A sealed hierarchy describing the state of the text generation. + */ +sealed interface SummarizeUiState { + + /** + * Empty state when the screen is first shown + */ + data object Initial: SummarizeUiState + + /** + * Still loading + */ + data object Loading: SummarizeUiState + + /** + * Text has been generated + */ + data class Success( + val outputText: String + ): SummarizeUiState + + /** + * There was an error generating text + */ + data class Error( + val errorMessage: String + ): SummarizeUiState +} diff --git a/app/src/main/java/helium314/keyboard/AIEngine/SummarizeViewModel.kt b/app/src/main/java/helium314/keyboard/AIEngine/SummarizeViewModel.kt new file mode 100644 index 000000000..353f54115 --- /dev/null +++ b/app/src/main/java/helium314/keyboard/AIEngine/SummarizeViewModel.kt @@ -0,0 +1,108 @@ +package helium314.keyboard.AIEngine + +import android.content.Context +import android.widget.Toast +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.ai.client.generativeai.GenerativeModel +import com.google.ai.client.generativeai.type.content +import helium314.keyboard.gemini.GeminiClient +import helium314.keyboard.latin.utils.Log +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +class SummarizeViewModel( + private val generativeModel: GenerativeModel +) : ViewModel() { + + private val _state = MutableStateFlow(AIState()) + val state: StateFlow = _state.asStateFlow() + + + private val _aiState = MutableLiveData() + val replyData: LiveData + get() = _aiState + + private val _isAICorrecting = MutableLiveData() + val isAICorrecting: LiveData + get() = _isAICorrecting + + private val _uiState: MutableStateFlow = + MutableStateFlow(SummarizeUiState.Initial) + val uiState: StateFlow = + _uiState.asStateFlow() + +// fun onAICorrection(context: Context) { +// +// val generativeModel = geminiClient.geminiFlashModel +// +// val inputContent = content { +// text("Please correct the following text for any spelling and grammatical errors, and slightly paraphrase it while keeping the original language and the markdown format:\n") +// } +// viewModelScope.launch { +// try { +// val response = generativeModel.generateContent(inputContent) +// _state.update { it.copy(isAICorrecting = true) } +// Toast.makeText(context, "Text Corrected With AIEngine", Toast.LENGTH_SHORT).show() +// } catch (e: Exception) { +// Toast.makeText(context, "Error Correcting Text With AIEngine", Toast.LENGTH_SHORT) +// .show() +// } finally { +// _state.update { it.copy(isAICorrecting = false) } +// } +// } +// } + + fun summarizeStreaming(inputText: String) { + _uiState.value = SummarizeUiState.Loading + + val prompt = + "Please correct the following text for any spelling and grammatical errors, and slightly paraphrase it while keeping the original language and the markdown format:\n: $inputText" + + viewModelScope.launch { + try { + var outputContent = "" + generativeModel.generateContentStream(prompt) + .collect { response -> + outputContent += response.text + _uiState.value = SummarizeUiState.Success(outputContent) + Log.d("SummarizeViewModel", "outputContent: $outputContent") + } + } catch (e: Exception) { + _uiState.value = SummarizeUiState.Error(e.localizedMessage ?: "") + Log.d("SummarizeViewModel", "Error: ${e.localizedMessage}") + } + } + } + +// fun summarizeStreamingLiveData(inputText: String) { +// //_uiState.value = SummarizeUiState.Loading +// +// val prompt = +// "Please correct the following text for any spelling and grammatical errors, and slightly paraphrase it while keeping the original language and the markdown format:\n: $inputText" +// +// viewModelScope.launch { +// try { +// var outputContent = "" +// val response = generativeModel.generateContentStream(prompt) +// val response = AIState(prompt) +// _aiState.postValue(response) +// +// //_aiState.value = generativeModel.generateContentStream(prompt) +// +//// .collect { response -> +//// outputContent += response.text +//// _uiState.value = SummarizeUiState.Success(outputContent) +//// } +// } catch (e: Exception) { +// _uiState.value = SummarizeUiState.Error(e.localizedMessage ?: "") +// } +// } +// } + +} \ No newline at end of file diff --git a/app/src/main/java/helium314/keyboard/AIEngine/SummarizeViewModelFactory.kt b/app/src/main/java/helium314/keyboard/AIEngine/SummarizeViewModelFactory.kt new file mode 100644 index 000000000..dbb1285a2 --- /dev/null +++ b/app/src/main/java/helium314/keyboard/AIEngine/SummarizeViewModelFactory.kt @@ -0,0 +1,20 @@ +package helium314.keyboard.AIEngine + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import com.google.ai.client.generativeai.GenerativeModel +import helium314.keyboard.gemini.GeminiClient + +class SummarizeViewModelFactory( + //private val geminiClient: GeminiClient, + private val generativeModel: GenerativeModel +) : ViewModelProvider.Factory { + + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(SummarizeViewModel::class.java)) { + @Suppress("UNCHECKED_CAST") + return SummarizeViewModel( generativeModel) as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } +} diff --git a/app/src/main/java/helium314/keyboard/bottomsheets/SheetOneFragment.kt b/app/src/main/java/helium314/keyboard/bottomsheets/SheetOneFragment.kt new file mode 100644 index 000000000..737f9233b --- /dev/null +++ b/app/src/main/java/helium314/keyboard/bottomsheets/SheetOneFragment.kt @@ -0,0 +1,65 @@ +package helium314.keyboard.bottomsheets + + +import android.content.Intent +import android.graphics.Color +import android.graphics.Typeface +import android.os.Bundle +import android.text.SpannableString +import android.text.Spanned +import android.text.style.ForegroundColorSpan +import android.text.style.StyleSpan +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.TextView +import helium314.keyboard.latin.R +import android.provider.Settings + +class SheetOneFragment : Fragment() { + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + // Inflate the layout for this fragment + var view = inflater.inflate(R.layout.fragment_sheet1, container, false) + + val instructionText = + "Tap the toggle to enable Oscar Keyboard in \n your keyboard list" + val spannableString = SpannableString(instructionText) + + val startIndex = instructionText.indexOf("Oscar Keyboard") + val endIndex = startIndex + "Oscar Keyboard".length + + if (startIndex >= 0) { + spannableString.setSpan( + ForegroundColorSpan(Color.BLACK), // Set color to black + startIndex, + endIndex, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) + + spannableString.setSpan( + StyleSpan(Typeface.BOLD), // Set style to bold + startIndex, + endIndex, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + + val textViewInstruction = view.findViewById(R.id.text_view_instruction) + textViewInstruction.text = spannableString + + val buttonEnable = view.findViewById