diff --git a/.idea/misc.xml b/.idea/misc.xml
index 8af44db9eeb0dcd2e1a0af963cc2638ed10dc07f..c4f46d89002337a8e7a280e2e6165b22258b399a 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -7,6 +7,7 @@
+
diff --git a/WHY_OFFICIAL.md b/WHY_OFFICIAL.md
new file mode 100644
index 0000000000000000000000000000000000000000..072b8bb3b69ae276bbd7a7f4add40decc4e3ae88
--- /dev/null
+++ b/WHY_OFFICIAL.md
@@ -0,0 +1,5 @@
+# Only download Gugal from official sources!
+
+Unofficial versions of Gugal can contain malware.
+
+Also, Gugal saves credentials which are used to search Google. An unofficial, malicious version of Gugal might steal those credentials and potentially cost you money, as the Custom Search Engine API is only free for 100 queries per day.
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 6d5ca49b227b32a8c9430b702c9cc56fb09be074..aa5f3b92a715201f9c6b52d2f44c14cbe8c5c5f5 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,68 +1,68 @@
-plugins {
- id 'com.android.application'
- id 'kotlin-android'
-}
-
-android {
- compileSdk 31
-
- defaultConfig {
- applicationId "com.porg.gugal"
- minSdk 27
- targetSdk 31
- versionCode 1100
- versionName "0.3"
-
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- vectorDrawables {
- useSupportLibrary true
- }
- }
-
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- }
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
- kotlinOptions {
- jvmTarget = '1.8'
- useIR = true
- }
- buildFeatures {
- compose true
- }
- composeOptions {
- kotlinCompilerExtensionVersion compose_version
- kotlinCompilerVersion '1.5.10'
- }
- packagingOptions {
- resources {
- excludes += '/META-INF/{AL2.0,LGPL2.1}'
- }
- }
-}
-
-dependencies {
-
- implementation 'androidx.core:core-ktx:1.6.0'
- implementation 'androidx.appcompat:appcompat:1.3.1'
- implementation 'com.google.android.material:material:1.4.0'
- implementation "androidx.compose.ui:ui:$compose_version"
- implementation "androidx.compose.material:material:$compose_version"
- implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
- implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
- implementation 'androidx.activity:activity-compose:1.3.1'
- implementation 'com.android.volley:volley:1.2.1'
- implementation 'androidx.security:security-crypto-ktx:1.1.0-alpha02'
- implementation 'androidx.compose.material3:material3:1.0.0-alpha05'
- testImplementation 'junit:junit:4.+'
- androidTestImplementation 'androidx.test.ext:junit:1.1.3'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
- androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
- debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
+plugins {
+ id 'com.android.application'
+ id 'kotlin-android'
+}
+
+android {
+ compileSdk 31
+
+ defaultConfig {
+ applicationId "com.porg.gugal"
+ minSdk 27
+ targetSdk 31
+ versionCode 1100
+ versionName "0.3 suw-test 2"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ vectorDrawables {
+ useSupportLibrary true
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ useIR = true
+ }
+ buildFeatures {
+ compose true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion compose_version
+ kotlinCompilerVersion '1.5.10'
+ }
+ packagingOptions {
+ resources {
+ excludes += '/META-INF/{AL2.0,LGPL2.1}'
+ }
+ }
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.6.0'
+ implementation 'androidx.appcompat:appcompat:1.3.1'
+ implementation 'com.google.android.material:material:1.5.0'
+ implementation "androidx.compose.ui:ui:$compose_version"
+ implementation "androidx.compose.material:material:$compose_version"
+ implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
+ implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
+ implementation 'androidx.activity:activity-compose:1.3.1'
+ implementation 'com.android.volley:volley:1.2.1'
+ implementation 'androidx.security:security-crypto-ktx:1.1.0-alpha02'
+ implementation 'androidx.compose.material3:material3:1.0.0-alpha09'
+ testImplementation 'junit:junit:4.+'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+ androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
+ debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 26242c613097adbcfcdecd91805e4dc88f5877fb..12ec609bd49e14b22a451f675dc13fa9558b40e9 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,7 +2,8 @@
-
+
+
+
-
-
@@ -31,12 +40,14 @@
-
-
+ android:exported="false"
+ android:theme="@style/Theme.Gugal.NoActionBar">
+
\ No newline at end of file
diff --git a/app/src/main/java/com/porg/gugal/MainActivity.kt b/app/src/main/java/com/porg/gugal/MainActivity.kt
index 5d20a0af8214880eb097d9773ef4da3221119c93..9f026e297aa9fd6bf9d70ea517289bee2ce47d1b 100644
--- a/app/src/main/java/com/porg/gugal/MainActivity.kt
+++ b/app/src/main/java/com/porg/gugal/MainActivity.kt
@@ -1,428 +1,442 @@
-/*
- * MainActivity.kt
- * Gugal
- * Copyright (c) 2021 thegreatporg
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package com.porg.gugal
-
-import android.content.ComponentName
-import android.content.Intent
-import android.content.SharedPreferences
-import android.net.Uri
-import android.os.Bundle
-import android.widget.Toast
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.compose.animation.ExperimentalAnimationApi
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.foundation.text.KeyboardActions
-import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.material.ExperimentalMaterialApi
-import androidx.compose.material.TextField
-import androidx.compose.material3.Button
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.mutableStateListOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.text.input.ImeAction
-import androidx.compose.ui.text.input.TextFieldValue
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import androidx.security.crypto.EncryptedSharedPreferences
-import androidx.security.crypto.MasterKeys
-import com.android.volley.Request
-import com.android.volley.RequestQueue
-import com.android.volley.toolbox.JsonObjectRequest
-import com.android.volley.toolbox.Volley
-import com.porg.gugal.setup.SetupStartActivity
-import com.porg.gugal.ui.theme.GugalTheme
-import com.porg.gugal.ui.theme.shapeScheme
-import org.json.JSONObject
-
-
-@ExperimentalMaterialApi
-class MainActivity : ComponentActivity() {
-
- private var apikey = ""
- private var cx = ""
- val cardElevation = 15.dp;
-
- @ExperimentalAnimationApi
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- val ca: List? = loadCxApi()
- if (ca?.get(0) ?: "none" != "none") cx = ca?.get(0) ?: ""
- if (ca?.get(1) ?: "none" != "none") apikey = ca?.get(1) ?: ""
-
- setContent {
- GugalTheme {
- // A surface container using the 'background' color from the theme
- Surface(color = MaterialTheme.colorScheme.background) {
- ResultPage()
- }
- }
- }
- }
-
- private fun loadCxApi(): List? {
- // Although you can define your own key generation parameter specification, it's
- // recommended that you use the value specified here.
- val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
- val mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)
-
- val sharedPrefsFile = "gugalprefs"
- val sharedPreferences: SharedPreferences = EncryptedSharedPreferences.create(
- sharedPrefsFile,
- mainKeyAlias,
- applicationContext,
- EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
- EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
- )
- val arr: ArrayList = ArrayList()
- sharedPreferences.getString("serp_google_data_cx", "none")?.let { arr.add(it) }
- sharedPreferences.getString("serp_google_data_ak", "none")?.let { arr.add(it) }
- return (arr.toArray() as? Array<*>)?.filterIsInstance()
- }
-
- private fun saveCxApi(cx: String, apikey: String) {
- // Although you can define your own key generation parameter specification, it's
- // recommended that you use the value specified here.
- val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
- val mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)
-
- val sharedPrefsFile = "gugalprefs"
- val sharedPreferences: SharedPreferences = EncryptedSharedPreferences.create(
- sharedPrefsFile,
- mainKeyAlias,
- applicationContext,
- EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
- EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
- )
-
- with (sharedPreferences.edit()) {
- // Edit the user's shared preferences...
- this.putString("serp_google_data_cx", cx)
- this.putString("serp_google_data_ak", apikey)
- apply()
- }
- }
-
- @ExperimentalAnimationApi
- @Composable
- fun ResultPage() {
- val textState = remember { mutableStateOf(TextFieldValue()) }
- Column {
- val res = remember { mutableStateListOf() }
-
- TextField(
- placeholder = { Text(text = "Search Google")},
- value = textState.value,
- modifier = Modifier
- .padding(all = 4.dp)
- .fillMaxWidth(),
- onValueChange = { nv ->
- textState.value = nv
- },
- keyboardActions = KeyboardActions(
- onSearch = {
- val queue: RequestQueue = Volley.newRequestQueue(applicationContext)
- val tsvt = textState.value.text
- val url = "https://customsearch.googleapis.com/customsearch/v1?cx=$cx&key=$apikey&q=$tsvt"
- // Request a string response from the provided URL.
- val jsonObjectRequest = JsonObjectRequest(Request.Method.GET, url, null,
- { response ->
- val items = response.getJSONArray("items")
- res.clear()
- for (i in 0 until items.length()) {
- val item: JSONObject = items.getJSONObject(i)
- res.add(Result(item.getString("title"), item.getString("snippet"),
- item.getString("link"), item.getString("displayLink")))
- }
- },
- { error ->
- error.message?.let {
- res.add(Result("Error", it, "",""))
- }
- }
- )
-
- // Access the RequestQueue through your singleton class.
- queue.add(jsonObjectRequest)
- }
- ),
- maxLines = 1,
- keyboardOptions = KeyboardOptions(
- imeAction = ImeAction.Search
- )
- )
-
- Results(
- results = res
- )
- }
- }
-
- @Composable
- fun ResultCard(res: Result) {
- Surface(
- shape = MaterialTheme.shapeScheme.medium,
- tonalElevation = cardElevation,
- modifier = Modifier
- .padding(all = 4.dp)
- .clickable(onClick = {
- if (res.url != "") {
- val intent = Intent(Intent.ACTION_VIEW, Uri.parse(res.url))
- // Note the Chooser below. If no applications match,
- // Android displays a system message.So here there is no need for try-catch.
- startActivity(Intent.createChooser(intent, "Open result in"))
- }
- }),
- ) {
- Column {
- Text(
- text = res.title,
- modifier = Modifier.padding(all = 4.dp),
- style = MaterialTheme.typography.titleLarge
- )
- if (res.domain != "") {
- Text(
- text = res.domain,
- modifier = Modifier.padding(top = 2.dp, bottom = 2.dp, start = 4.dp, end = 4.dp),
- style = MaterialTheme.typography.titleSmall
- )
- }
- // Add a vertical space between the author and message texts
- Spacer(modifier = Modifier.height(2.dp))
- Text(
- text = res.body,
- modifier = Modifier.padding(all = 4.dp),
- style = MaterialTheme.typography.bodyLarge
- )
- }
- }
- }
-
- @Composable
- @ExperimentalAnimationApi
- fun CxApiCard() {
- val _cx = remember { mutableStateOf(TextFieldValue()) }
- val _ak = remember { mutableStateOf(TextFieldValue()) }
- Surface(
- shape = MaterialTheme.shapeScheme.medium,
- tonalElevation = cardElevation,
- modifier = Modifier.padding(all = 4.dp),
- ) {
- Column {
- Text(
- text = "Configuration",
- modifier = Modifier.padding(all = 4.dp),
- style = MaterialTheme.typography.titleLarge
- )
- Text(
- text = "Go to cse.google.com. Click Add, name your engine," +
- " select \"Search the entire web\" and enable image search. Click Customize," +
- " copy the search engine ID and paste it here:",
- modifier = Modifier.padding(all = 4.dp),
- style = MaterialTheme.typography.bodyLarge
- )
- TextField(
- placeholder = { Text(text = "CSE ID") },
- value = _cx.value,
- modifier = Modifier
- .padding(all = 4.dp)
- .fillMaxWidth(),
- onValueChange = { nv ->
- _cx.value = nv
- },
- keyboardActions = KeyboardActions(
- onDone = {
- cx = _cx.value.text
- }
- ),
- maxLines = 1,
- keyboardOptions = KeyboardOptions(
- imeAction = ImeAction.Done
- )
- )
- Text(
- text = "Now scroll down to \"Programmatic access\", then tap \"Get started\"" +
- " next to \"Custom Search JSON API\". Click \"Get a key\", go through the setup and paste the API key here:",
- modifier = Modifier.padding(all = 4.dp),
- style = MaterialTheme.typography.bodyLarge
- )
- TextField(
- placeholder = { Text(text = "API key")},
- value = _ak.value,
- modifier = Modifier
- .padding(all = 4.dp)
- .fillMaxWidth(),
- onValueChange = { nv ->
- _ak.value = nv
- },
- keyboardActions = KeyboardActions(
- onDone = {
- apikey = _ak.value.text
- }
- ),
- maxLines = 1,
- keyboardOptions = KeyboardOptions(
- imeAction = ImeAction.Done
- )
- )
- Text(
- text = "I recommend tapping the \"API Console\" link, and restricting the API key to Custom Search API.",
- modifier = Modifier.padding(all = 4.dp),
- style = MaterialTheme.typography.bodyLarge
- )
- Button(
- modifier = Modifier
- .fillMaxWidth()
- .padding(all = 4.dp),
- onClick = {
- if (apikey == "") apikey = _ak.value.text
- if (cx == "") cx = _cx.value.text
- saveCxApi(cx,apikey)
- if (apikey != "" && cx != "")
- Toast.makeText(applicationContext, "Configuration complete!", Toast.LENGTH_LONG).show()
- }
- ) {
- Text("Save")
- }
- }
- }
- }
-
- @Composable
- @Preview
- fun PreviewResultCard() {
- GugalTheme {
- ResultCard(
- res = Result("Colleague", "Hey, take a look at Jetpack Compose, it's great!", "about:blank", "test.com")
- )
- }
- }
-
- @ExperimentalAnimationApi
- @Composable
- fun Results(results: List) {
- if (cx.isEmpty() && apikey.isEmpty()) {
- val intent = Intent(this, SetupStartActivity::class.java)
- intent.component =
- ComponentName("com.porg.gugal", "com.porg.gugal.setup.SetupStartActivity")
- startActivity(intent)
- InfoCard()
- CxApiCard()
- DonateCard()
- }
- LazyColumn {
- items(results) { message ->
- ResultCard(message)
- }
- }
- }
-
- @ExperimentalAnimationApi
- @Composable
- @Preview
- fun ResultsPreview() {
- GugalTheme {
- Results(
- results = List(size = 3) {
- Result(
- "Google privacy scandal",
- "Google have been fined for antitrust once again, a few days after people bought more than 5 Pixels.",
- "about:blank",
- "test.com"
- )
- }
- )
- }
- }
-
-
- @Composable
- @ExperimentalAnimationApi
- fun DonateCard() {
- Surface(
- shape = MaterialTheme.shapeScheme.medium,
- tonalElevation = cardElevation,
- modifier = Modifier
- .padding(all = 4.dp)
- .clickable(
- onClick = {
- val intent = Intent(
- Intent.ACTION_VIEW,
- Uri.parse("https://ko-fi.com/thegreatporg")
- )
- // Note the Chooser below. If no applications match,
- // Android displays a system message.So here there is no need for try-catch.
- startActivity(Intent.createChooser(intent, "Open in"))
- }
- )
- ) {
- Column {
- Text(
- text = "Donate",
- modifier = Modifier.padding(all = 4.dp),
- style = MaterialTheme.typography.titleLarge
- )
- Text(
- text = "Gugal is free and open-source, and to my knowledge there are no privacy" +
- " respecting ad services with Android support. So, if you like Gugal, " +
- "you can donate to me on Ko-fi. Tap on this card to open the donation page.",
- modifier = Modifier.padding(all = 4.dp),
- style = MaterialTheme.typography.bodyLarge
- )
- }
- }
- }
-
- @Composable
- @ExperimentalAnimationApi
- fun InfoCard() {
- Surface(
- shape = MaterialTheme.shapeScheme.medium,
- tonalElevation = cardElevation,
- modifier = Modifier.padding(all = 4.dp)
- ) {
- Column {
- Text(
- text = "Welcome",
- modifier = Modifier.padding(all = 4.dp),
- style = MaterialTheme.typography.titleLarge
- )
- Text(
- text = "Gugal is an alternative to the Google app. " +
- "It uses the Google Custom Search Engine API, so unfortunately " +
- "Google can still track your queries and potentially link " +
- "them to you. However, Gugal is lighter than the official Google " +
- "app and has no trackers built in. If you just need web search this " +
- "app is for you.",
- modifier = Modifier.padding(all = 4.dp),
- style = MaterialTheme.typography.bodyLarge
- )
- }
- }
- }
+/*
+ * MainActivity.kt
+ * Gugal
+ * Copyright (c) 2021 thegreatporg
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.porg.gugal
+
+import android.content.ComponentName
+import android.content.Intent
+import android.content.SharedPreferences
+import android.net.Uri
+import android.os.Bundle
+import android.widget.Toast
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.text.KeyboardActions
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material3.TextField
+import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.security.crypto.EncryptedSharedPreferences
+import androidx.security.crypto.MasterKeys
+import com.android.volley.Request
+import com.android.volley.RequestQueue
+import com.android.volley.toolbox.JsonObjectRequest
+import com.android.volley.toolbox.Volley
+import com.porg.gugal.providers.SerpProvider
+import com.porg.gugal.providers.cse.GoogleCseSerp
+import com.porg.gugal.setup.SetupStartActivity
+import com.porg.gugal.ui.theme.GugalTheme
+import com.porg.gugal.ui.theme.shapeScheme
+import org.json.JSONObject
+
+
+@ExperimentalMaterialApi
+class MainActivity : ComponentActivity() {
+
+ // TODO SPFW: make SERP provider selectable
+ val serpProvider: SerpProvider = GoogleCseSerp()
+
+ private var apikey = ""
+ private var cx = ""
+ val cardElevation = 15.dp;
+
+ @ExperimentalAnimationApi
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val ca: List? = loadCxApi()
+ if (ca?.get(0) ?: "none" != "none") cx = ca?.get(0) ?: ""
+ if (ca?.get(1) ?: "none" != "none") apikey = ca?.get(1) ?: ""
+
+ setContent {
+ GugalTheme {
+ // A surface container using the 'background' color from the theme
+ Surface(color = MaterialTheme.colorScheme.background) {
+ ResultPage()
+ }
+ }
+ }
+ }
+
+ private fun loadCxApi(): List? {
+ // Although you can define your own key generation parameter specification, it's
+ // recommended that you use the value specified here.
+ val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
+ val mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)
+
+ val sharedPrefsFile = "gugalprefs"
+ val sharedPreferences: SharedPreferences = EncryptedSharedPreferences.create(
+ sharedPrefsFile,
+ mainKeyAlias,
+ applicationContext,
+ EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
+ EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
+ )
+ val arr: ArrayList = ArrayList()
+ sharedPreferences.getString("serp_${serpProvider.id}_cx", "none")?.let { arr.add(it) }
+ sharedPreferences.getString("serp_${serpProvider.id}_ak", "none")?.let { arr.add(it) }
+ return (arr.toArray() as? Array<*>)?.filterIsInstance()
+ }
+
+ @Deprecated("Replaced with new setup wizard")
+ private fun saveCxApi(cx: String, apikey: String) {
+ // Although you can define your own key generation parameter specification, it's
+ // recommended that you use the value specified here.
+ val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
+ val mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)
+
+ val sharedPrefsFile = "gugalprefs"
+ val sharedPreferences: SharedPreferences = EncryptedSharedPreferences.create(
+ sharedPrefsFile,
+ mainKeyAlias,
+ applicationContext,
+ EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
+ EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
+ )
+
+ with (sharedPreferences.edit()) {
+ // Edit the user's shared preferences...
+ this.putString("serp_google_data_cx", cx)
+ this.putString("serp_google_data_ak", apikey)
+ apply()
+ }
+ }
+
+ @ExperimentalAnimationApi
+ @Composable
+ fun ResultPage() {
+ val textState = remember { mutableStateOf(TextFieldValue()) }
+ Column {
+ val res = remember { mutableStateListOf() }
+
+ TextField(
+ placeholder = { Text(text = "Search Google")},
+ value = textState.value,
+ modifier = Modifier
+ .padding(all = 4.dp)
+ .fillMaxWidth(),
+ onValueChange = { nv ->
+ textState.value = nv
+ },
+ keyboardActions = KeyboardActions(
+ onSearch = {
+ val queue: RequestQueue = Volley.newRequestQueue(applicationContext)
+ val tsvt = textState.value.text
+ val url = "https://customsearch.googleapis.com/customsearch/v1?cx=$cx&key=$apikey&q=$tsvt"
+ // Request a string response from the provided URL.
+ val jsonObjectRequest = JsonObjectRequest(Request.Method.GET, url, null,
+ { response ->
+ val items = response.getJSONArray("items")
+ res.clear()
+ for (i in 0 until items.length()) {
+ val item: JSONObject = items.getJSONObject(i)
+ if (item.has("snippet"))
+ res.add(Result(item.getString("title"), item.getString("snippet"),
+ item.getString("link"), item.getString("displayLink")))
+ else
+ res.add(Result(item.getString("title"), null,
+ item.getString("link"), item.getString("displayLink")))
+ }
+ },
+ { error ->
+ error.message?.let {
+ res.add(Result("Error", it, "",""))
+ }
+ }
+ )
+
+ // Access the RequestQueue through your singleton class.
+ queue.add(jsonObjectRequest)
+ }
+ ),
+ maxLines = 1,
+ keyboardOptions = KeyboardOptions(
+ imeAction = ImeAction.Search
+ )
+ )
+
+ Results(
+ results = res
+ )
+ }
+ }
+
+ @Composable
+ fun ResultCard(res: Result) {
+ Surface(
+ shape = MaterialTheme.shapeScheme.medium,
+ tonalElevation = cardElevation,
+ modifier = Modifier
+ .padding(all = 4.dp).fillMaxWidth()
+ .clickable(onClick = {
+ if (res.url != "") {
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse(res.url))
+ // Note the Chooser below. If no applications match,
+ // Android displays a system message.So here there is no need for try-catch.
+ startActivity(Intent.createChooser(intent, "Open result in"))
+ }
+ }),
+ ) {
+ Column {
+ Text(
+ text = res.title,
+ modifier = Modifier.padding(all = 4.dp),
+ style = MaterialTheme.typography.titleLarge
+ )
+ if (res.domain != "") {
+ Text(
+ text = res.domain,
+ modifier = Modifier.padding(top = 2.dp, bottom = 2.dp, start = 4.dp, end = 4.dp),
+ style = MaterialTheme.typography.titleSmall
+ )
+ }
+ if (res.body != null) {
+ // Add a vertical space between the author and message texts
+ Spacer(modifier = Modifier.height(2.dp))
+ Text(
+ text = res.body,
+ modifier = Modifier.padding(all = 4.dp),
+ style = MaterialTheme.typography.bodyLarge
+ )
+ }
+ }
+ }
+ }
+
+ @Composable
+ @ExperimentalAnimationApi
+ @Deprecated("Replaced with new setup wizard")
+ fun CxApiCard() {
+ val _cx = remember { mutableStateOf(TextFieldValue()) }
+ val _ak = remember { mutableStateOf(TextFieldValue()) }
+ Surface(
+ shape = MaterialTheme.shapeScheme.medium,
+ tonalElevation = cardElevation,
+ modifier = Modifier.padding(all = 4.dp),
+ ) {
+ Column {
+ Text(
+ text = "Configuration",
+ modifier = Modifier.padding(all = 4.dp),
+ style = MaterialTheme.typography.titleLarge
+ )
+ Text(
+ text = "Go to cse.google.com. Click Add, name your engine," +
+ " select \"Search the entire web\" and enable image search. Click Customize," +
+ " copy the search engine ID and paste it here:",
+ modifier = Modifier.padding(all = 4.dp),
+ style = MaterialTheme.typography.bodyLarge
+ )
+ TextField(
+ placeholder = { Text(text = "CSE ID") },
+ value = _cx.value,
+ modifier = Modifier
+ .padding(all = 4.dp)
+ .fillMaxWidth(),
+ onValueChange = { nv ->
+ _cx.value = nv
+ },
+ keyboardActions = KeyboardActions(
+ onDone = {
+ cx = _cx.value.text
+ }
+ ),
+ maxLines = 1,
+ keyboardOptions = KeyboardOptions(
+ imeAction = ImeAction.Done
+ )
+ )
+ Text(
+ text = "Now scroll down to \"Programmatic access\", then tap \"Get started\"" +
+ " next to \"Custom Search JSON API\". Click \"Get a key\", go through the setup and paste the API key here:",
+ modifier = Modifier.padding(all = 4.dp),
+ style = MaterialTheme.typography.bodyLarge
+ )
+ TextField(
+ placeholder = { Text(text = "API key")},
+ value = _ak.value,
+ modifier = Modifier
+ .padding(all = 4.dp)
+ .fillMaxWidth(),
+ onValueChange = { nv ->
+ _ak.value = nv
+ },
+ keyboardActions = KeyboardActions(
+ onDone = {
+ apikey = _ak.value.text
+ }
+ ),
+ maxLines = 1,
+ keyboardOptions = KeyboardOptions(
+ imeAction = ImeAction.Done
+ )
+ )
+ Text(
+ text = "I recommend tapping the \"API Console\" link, and restricting the API key to Custom Search API.",
+ modifier = Modifier.padding(all = 4.dp),
+ style = MaterialTheme.typography.bodyLarge
+ )
+ Button(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(all = 4.dp),
+ onClick = {
+ if (apikey == "") apikey = _ak.value.text
+ if (cx == "") cx = _cx.value.text
+ saveCxApi(cx,apikey)
+ if (apikey != "" && cx != "")
+ Toast.makeText(applicationContext, "Configuration complete!", Toast.LENGTH_LONG).show()
+ }
+ ) {
+ Text("Save")
+ }
+ }
+ }
+ }
+
+ @Composable
+ @Preview
+ fun PreviewResultCard() {
+ GugalTheme {
+ ResultCard(
+ res = Result("Colleague", "Hey, take a look at Jetpack Compose, it's great!", "about:blank", "test.com")
+ )
+ }
+ }
+
+ @ExperimentalAnimationApi
+ @Composable
+ fun Results(results: List) {
+ if (cx.isEmpty() && apikey.isEmpty()) {
+ val intent = Intent(this, SetupStartActivity::class.java)
+ intent.component =
+ ComponentName("com.porg.gugal", "com.porg.gugal.setup.SetupStartActivity")
+ startActivity(intent)
+ InfoCard()
+ CxApiCard()
+ DonateCard()
+ }
+ LazyColumn {
+ items(results) { message ->
+ ResultCard(message)
+ }
+ }
+ }
+
+ @ExperimentalAnimationApi
+ @Composable
+ @Preview
+ fun ResultsPreview() {
+ GugalTheme {
+ Results(
+ results = List(size = 3) {
+ Result(
+ "Google privacy scandal",
+ "Google have been fined for antitrust once again, a few days after people bought more than 5 Pixels.",
+ "about:blank",
+ "test.com"
+ )
+ }
+ )
+ }
+ }
+
+
+ @Composable
+ @ExperimentalAnimationApi
+ fun DonateCard() {
+ Surface(
+ shape = MaterialTheme.shapeScheme.medium,
+ tonalElevation = cardElevation,
+ modifier = Modifier
+ .padding(all = 4.dp)
+ .clickable(
+ onClick = {
+ val intent = Intent(
+ Intent.ACTION_VIEW,
+ Uri.parse("https://ko-fi.com/thegreatporg")
+ )
+ // Note the Chooser below. If no applications match,
+ // Android displays a system message.So here there is no need for try-catch.
+ startActivity(Intent.createChooser(intent, "Open in"))
+ }
+ )
+ ) {
+ Column {
+ Text(
+ text = "Donate",
+ modifier = Modifier.padding(all = 4.dp),
+ style = MaterialTheme.typography.titleLarge
+ )
+ Text(
+ text = "Gugal is free and open-source, and to my knowledge there are no privacy" +
+ " respecting ad services with Android support. So, if you like Gugal, " +
+ "you can donate to me on Ko-fi. Tap on this card to open the donation page.",
+ modifier = Modifier.padding(all = 4.dp),
+ style = MaterialTheme.typography.bodyLarge
+ )
+ }
+ }
+ }
+
+ @Composable
+ @ExperimentalAnimationApi
+ @Deprecated("Replaced with new setup wizard")
+ fun InfoCard() {
+ Surface(
+ shape = MaterialTheme.shapeScheme.medium,
+ tonalElevation = cardElevation,
+ modifier = Modifier.padding(all = 4.dp)
+ ) {
+ Column {
+ Text(
+ text = "Welcome",
+ modifier = Modifier.padding(all = 4.dp),
+ style = MaterialTheme.typography.titleLarge
+ )
+ Text(
+ text = "Gugal is an alternative to the Google app. " +
+ "It uses the Google Custom Search Engine API, so unfortunately " +
+ "Google can still track your queries and potentially link " +
+ "them to you. However, Gugal is lighter than the official Google " +
+ "app and has no trackers built in. If you just need web search this " +
+ "app is for you.",
+ modifier = Modifier.padding(all = 4.dp),
+ style = MaterialTheme.typography.bodyLarge
+ )
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/porg/gugal/Material3SetupWizard.kt b/app/src/main/java/com/porg/gugal/Material3SetupWizard.kt
new file mode 100644
index 0000000000000000000000000000000000000000..275e1bfa1c1e1b0e52a2bf09804206604d362329
--- /dev/null
+++ b/app/src/main/java/com/porg/gugal/Material3SetupWizard.kt
@@ -0,0 +1,138 @@
+/*
+ * Material3SetupWizard.kt
+ * Gugal
+ * Copyright (c) 2022 thegreatporg
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.porg.gugal
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material3.*
+import androidx.compose.material3.FloatingActionButtonDefaults.elevation
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.BlendMode
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import com.google.android.material.color.MaterialColors
+
+class Material3SetupWizard {
+
+ companion object {
+ @Composable
+ fun Header(text: String, doFinish: () -> Unit) {
+ IconButton(
+ onClick =doFinish,
+ modifier = Modifier.
+ then(Modifier.padding(start = 16.dp, top = 16.dp, bottom = 0.dp, end = 16.dp))
+ ) {
+ Icon(
+ imageVector = Icons.Filled.ArrowBack,
+ contentDescription = "Go back",
+ )
+ }
+ Text(
+ text = text,
+ modifier = Modifier
+ .padding(start = 24.dp, top = 72.dp, bottom = 24.dp, end = 24.dp)
+ .fillMaxWidth(),
+ style = MaterialTheme.typography.displaySmall
+ )
+ }
+
+ @Composable
+ fun TwoButtons(positive: () -> Unit, positiveText: String, negative: () -> Unit, negativeText: String = "Back") {
+ Box(
+ modifier = Modifier.fillMaxSize().then(PaddingModifier),
+ Alignment.BottomCenter
+ ) {
+ Surface(modifier = Modifier.fillMaxWidth(),color = MaterialTheme.colorScheme.background, tonalElevation = 0.dp) {
+ Box (modifier = Modifier.fillMaxWidth()) {
+ Button(
+ modifier = Modifier.padding(all = 4.dp).align(Alignment.BottomEnd),
+ onClick = positive
+ ) {
+ Text(positiveText)
+ }
+ if (negative != null) {
+ OutlinedButton(
+ modifier = Modifier.padding(all = 4.dp).align(Alignment.BottomStart),
+ onClick = negative
+ ) {
+ Text(negativeText)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @OptIn(ExperimentalMaterial3Api::class)
+ @Composable
+ fun Tip(text: String, modifier: Modifier, image: Int, onClick: (() -> Unit)?) {
+ if (onClick != null) {
+ Surface(
+ modifier = modifier,
+ shape = RoundedCornerShape(20.dp),
+ tonalElevation = 2.dp,
+ onClick = onClick
+ ) {TipContent(text, image)}
+ } else {
+ Surface(
+ modifier = modifier,
+ shape = RoundedCornerShape(20.dp),
+ tonalElevation = 2.dp
+ ) {TipContent(text, image)}
+ }
+ }
+
+ @Composable
+ private fun TipContent(text: String, image: Int) {
+ Row(
+ modifier = Modifier.padding(all = 14.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Image(
+ painterResource(image),
+ modifier = Modifier.size(36.dp),
+ colorFilter = ColorFilter.tint(
+ MaterialTheme.colorScheme.secondary
+ ),
+ contentDescription = ""
+ )
+ Text(
+ text = text,
+ modifier = Modifier.padding(start = 14.dp),
+ style = MaterialTheme.typography.bodyMedium
+ )
+ }
+ }
+
+ @Composable
+ fun Tip(text: String, modifier: Modifier, image: Int) {
+ Tip(text, modifier, image, null)
+ }
+
+ val PaddingModifier: Modifier = Modifier.padding(16.dp)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/porg/gugal/Result.kt b/app/src/main/java/com/porg/gugal/Result.kt
index c4fedfa0c5179ab1b94aff26db0799dda5a215ec..62f0e54cde19a2717623860f673ca6be1677275f 100644
--- a/app/src/main/java/com/porg/gugal/Result.kt
+++ b/app/src/main/java/com/porg/gugal/Result.kt
@@ -1,22 +1,22 @@
-/*
- * Result.kt
- * Gugal
- * Copyright (c) 2021 thegreatporg
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package com.porg.gugal
-
-data class Result(val title: String, val body: String, val url: String, val domain: String)
\ No newline at end of file
+/*
+ * Result.kt
+ * Gugal
+ * Copyright (c) 2021 thegreatporg
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.porg.gugal
+
+data class Result(val title: String, val body: String?, val url: String, val domain: String)
\ No newline at end of file
diff --git a/app/src/main/java/com/porg/gugal/providers/DummySerp.kt b/app/src/main/java/com/porg/gugal/providers/DummySerp.kt
index 57bafa37ed14ae94682a23f19a7c2678348fa752..232b3389d0bf5d4ee7c5b4cf2d4ad1c8e0faafe5 100644
--- a/app/src/main/java/com/porg/gugal/providers/DummySerp.kt
+++ b/app/src/main/java/com/porg/gugal/providers/DummySerp.kt
@@ -19,6 +19,8 @@
package com.porg.gugal.providers
+import android.content.Context
+import android.widget.Toast
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@@ -31,13 +33,17 @@ class DummySerp: SerpProvider {
get() = "225fa1a8022149648dd989f7a803360c-dummy"
@Composable
- override fun ConfigCardContents() {
+ override fun ConfigComposable(modifier: Modifier) {
Text(
text = "This is a dummy provider. Please change the provider to search the web.",
- modifier = Modifier.padding(all = 4.dp),
+ modifier = Modifier.padding(all = 4.dp).then(modifier)
)
}
+ override fun getSensitiveCredentials(): Map {
+ return mapOf("token" to "123456789abcdef")
+ }
+
override fun search(query: String): Array {
return Array(20,){Result("Dummy result $it",
"A search was performed using the dummy provider. Please change the provider to search the web.",
diff --git a/app/src/main/java/com/porg/gugal/providers/SerpProvider.kt b/app/src/main/java/com/porg/gugal/providers/SerpProvider.kt
index ec9e7a0855fda10de01156bea28c6f0cffb89681..21a8c7c377b3fed7c089247d9c2eef5008c456fc 100644
--- a/app/src/main/java/com/porg/gugal/providers/SerpProvider.kt
+++ b/app/src/main/java/com/porg/gugal/providers/SerpProvider.kt
@@ -19,19 +19,27 @@
package com.porg.gugal.providers
+import android.content.Context
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
import com.porg.gugal.Result
+import java.util.*
interface SerpProvider {
- @Composable fun ConfigCardContents() {}
+ @Composable fun ConfigComposable(modifier: Modifier) {}
fun search(query: String): Array {
return Array(0){null}
}
+ fun getSensitiveCredentials(): Map {
+ return mapOf("key" to "value")
+ }
+
companion object
val Companion.id: String get() = ""
+ val id: String get() = Companion.id
val providerInfo: ProviderInfo?
}
\ No newline at end of file
diff --git a/app/src/main/java/com/porg/gugal/providers/cse/GoogleCseSerp.kt b/app/src/main/java/com/porg/gugal/providers/cse/GoogleCseSerp.kt
index e2e78d2c49334feb17444d5cdf00868553724017..d5d2ab51c7f825275f8bbd10b095e087c1e5faa1 100644
--- a/app/src/main/java/com/porg/gugal/providers/cse/GoogleCseSerp.kt
+++ b/app/src/main/java/com/porg/gugal/providers/cse/GoogleCseSerp.kt
@@ -19,14 +19,16 @@
package com.porg.gugal.providers.cse
+import android.content.Context
+import android.widget.Toast
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
-import androidx.compose.material.TextField
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -36,19 +38,22 @@ import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import com.porg.gugal.providers.ProviderInfo
import com.porg.gugal.providers.SerpProvider
+import java.util.*
class GoogleCseSerp: SerpProvider {
@Composable
- override fun ConfigCardContents() {
+ override fun ConfigComposable(modifier: Modifier) {
val _cx = remember { mutableStateOf(TextFieldValue()) }
val _ak = remember { mutableStateOf(TextFieldValue()) }
- Column {
+ Column(
+ modifier = Modifier.padding(all = 4.dp).then(modifier)
+ ) {
Text(
text = "Go to cse.google.com. Click Add, name your engine," +
" select \"Search the entire web\" and enable image search. Click Customize," +
" copy the search engine ID and paste it here:",
modifier = Modifier.padding(all = 4.dp),
- style = MaterialTheme.typography.caption
+ style = MaterialTheme.typography.bodyLarge
)
TextField(
placeholder = { Text(text = "CSE ID") },
@@ -58,6 +63,7 @@ class GoogleCseSerp: SerpProvider {
.fillMaxWidth(),
onValueChange = { nv ->
_cx.value = nv
+ cx = _cx.value.text
},
keyboardActions = KeyboardActions(
onDone = {
@@ -73,7 +79,7 @@ class GoogleCseSerp: SerpProvider {
text = "Now scroll down to \"Programmatic access\", then tap \"Get started\"" +
" next to \"Custom Search JSON API\". Click \"Get a key\", go through the setup and paste the API key here:",
modifier = Modifier.padding(all = 4.dp),
- style = MaterialTheme.typography.caption
+ style = MaterialTheme.typography.bodyLarge
)
TextField(
placeholder = { Text(text = "API key") },
@@ -83,6 +89,7 @@ class GoogleCseSerp: SerpProvider {
.fillMaxWidth(),
onValueChange = { nv ->
_ak.value = nv
+ apikey = nv.text
},
keyboardActions = KeyboardActions(
onDone = {
@@ -97,11 +104,15 @@ class GoogleCseSerp: SerpProvider {
Text(
text = "I recommend tapping the \"API Console\" link, and restricting the API key to Custom Search API.",
modifier = Modifier.padding(all = 4.dp),
- style = MaterialTheme.typography.caption
+ style = MaterialTheme.typography.bodyLarge
)
}
}
+ override fun getSensitiveCredentials(): Map {
+ return mapOf("ak" to apikey, "cx" to cx)
+ }
+
private var apikey = ""
private var cx = ""
diff --git a/app/src/main/java/com/porg/gugal/setup/SetupConfigureSerpActivity.kt b/app/src/main/java/com/porg/gugal/setup/SetupConfigureSerpActivity.kt
new file mode 100644
index 0000000000000000000000000000000000000000..aca163a2104556352c0392de130968bc7385f191
--- /dev/null
+++ b/app/src/main/java/com/porg/gugal/setup/SetupConfigureSerpActivity.kt
@@ -0,0 +1,105 @@
+/*
+ * SetupConfigureSerpActivity.kt
+ * Gugal
+ * Copyright (c) 2022 thegreatporg
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.porg.gugal.setup
+
+import android.content.ComponentName
+import android.content.Intent
+import android.content.SharedPreferences
+import android.os.Bundle
+import android.util.Log
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.layout.*
+import androidx.compose.material3.*
+import androidx.compose.ui.Modifier
+import androidx.security.crypto.EncryptedSharedPreferences
+import androidx.security.crypto.MasterKeys
+import com.porg.gugal.Material3SetupWizard
+import com.porg.gugal.R
+import com.porg.gugal.providers.SerpProvider
+import com.porg.gugal.providers.cse.GoogleCseSerp
+import com.porg.gugal.ui.theme.GugalTheme
+
+class SetupConfigureSerpActivity : ComponentActivity() {
+
+ // TODO SPFW: make SERP provider selectable
+ val serpProvider: SerpProvider = GoogleCseSerp()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ GugalTheme {
+ // A surface container using the 'background' color from the theme
+ Surface(color = MaterialTheme.colorScheme.background) {
+ Material3SetupWizard.TwoButtons(
+ positive = { saveSensitive(serpProvider) },
+ positiveText = "Save",
+ negative = { finish() }
+ )
+ Column(
+ modifier = Modifier.fillMaxSize()
+ ) {
+ Material3SetupWizard.Header(
+ text = getText(R.string.setup_p3_title).toString(),
+ doFinish = { finish() }
+ )
+ serpProvider.ConfigComposable(
+ modifier = Material3SetupWizard.PaddingModifier
+ )
+ }
+ }
+ }
+ }
+ }
+
+ private fun saveSensitive(serpProvider: SerpProvider) {
+ // Get the sensitive data
+ val sensitive = serpProvider.getSensitiveCredentials()
+ Log.d("gugal", sensitive.size.toString())
+
+ // Although you can define your own key generation parameter specification, it's
+ // recommended that you use the value specified here.
+ val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
+ val mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)
+
+ val sharedPrefsFile = "gugalprefs"
+ val sharedPreferences: SharedPreferences = EncryptedSharedPreferences.create(
+ sharedPrefsFile,
+ mainKeyAlias,
+ applicationContext,
+ EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
+ EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
+ )
+
+ with (sharedPreferences.edit()) {
+ // Edit the user's shared preferences...
+ for (i in sensitive) {
+ this.putString("serp_${serpProvider.id}_${i.key}", i.value)
+ }
+ apply()
+ }
+
+ val intent = Intent(applicationContext, SetupFOSSActivity::class.java)
+ intent.component =
+ ComponentName("com.porg.gugal", "com.porg.gugal.setup.SetupFOSSActivity")
+ startActivity(intent)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/porg/gugal/setup/SetupFOSSActivity.kt b/app/src/main/java/com/porg/gugal/setup/SetupFOSSActivity.kt
new file mode 100644
index 0000000000000000000000000000000000000000..44a4dc3bb685c2cdd1a427f9b900d1e16a35d559
--- /dev/null
+++ b/app/src/main/java/com/porg/gugal/setup/SetupFOSSActivity.kt
@@ -0,0 +1,96 @@
+/*
+ * SetupFOSSActivity.kt
+ * Gugal
+ * Copyright (c) 2022 thegreatporg
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.porg.gugal.setup
+
+import android.content.ComponentName
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material3.*
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.porg.gugal.MainActivity
+import com.porg.gugal.Material3SetupWizard
+import com.porg.gugal.Material3SetupWizard.Companion.Tip
+import com.porg.gugal.R
+import com.porg.gugal.ui.theme.GugalTheme
+
+class SetupFOSSActivity : ComponentActivity() {
+ @OptIn(ExperimentalMaterialApi::class)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ GugalTheme {
+ // A surface container using the 'background' color from the theme
+ Surface(color = MaterialTheme.colorScheme.background) {
+ Material3SetupWizard.TwoButtons(
+ positive = {
+ val intent = Intent(applicationContext, MainActivity::class.java)
+ intent.component =
+ ComponentName("com.porg.gugal", "com.porg.gugal.MainActivity")
+ startActivity(intent)
+ },
+ positiveText = "Next",
+ negative = {
+ finish()
+ }
+ )
+ Column(
+ modifier = Modifier.fillMaxSize()
+ ) {
+ Material3SetupWizard.Header(
+ text = getText(R.string.setup_p4_title).toString(),
+ doFinish = { finish() }
+ )
+ Text(
+ text = getText(R.string.setup_p4_description).toString(),
+ modifier = Modifier
+ .padding(start = 24.dp, top = 0.dp, bottom = 24.dp, end = 24.dp)
+ .fillMaxWidth(),
+ style = MaterialTheme.typography.bodyLarge
+ )
+ Tip(
+ text = getText(R.string.setup_p4_tip_1).toString(),
+ modifier = Modifier.padding(start = 24.dp, top = 0.dp, bottom = 24.dp, end = 24.dp),
+ image = R.drawable.ic_warning,
+ onClick = {
+ val intent = Intent(Intent.ACTION_VIEW,
+ Uri.parse("https://gitlab.com/narektor/gugal/-/blob/setup-wizard/WHY_OFFICIAL.md"))
+ // Note the Chooser below. If no applications match,
+ // Android displays a system message.So here there is no need for try-catch.
+ startActivity(Intent.createChooser(intent, "Open result in"))
+ }
+ )
+ Tip(
+ text = getText(R.string.setup_p4_tip_2).toString(),
+ modifier = Modifier.padding(start = 24.dp, top = 0.dp, bottom = 24.dp, end = 24.dp),
+ image = R.drawable.ic_donate
+ )
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/porg/gugal/setup/SetupStartActivity.kt b/app/src/main/java/com/porg/gugal/setup/SetupStartActivity.kt
index beda7e6a71cf84ff6903d13654a13ee99b112861..0f7a58544eba7edb9ee0600d3dcf44ecf436e8fb 100644
--- a/app/src/main/java/com/porg/gugal/setup/SetupStartActivity.kt
+++ b/app/src/main/java/com/porg/gugal/setup/SetupStartActivity.kt
@@ -19,6 +19,8 @@
package com.porg.gugal.setup
+import android.content.ComponentName
+import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
@@ -82,7 +84,10 @@ class SetupStartActivity : ComponentActivity() {
.fillMaxWidth()
.padding(all = 16.dp),
onClick = {
- Toast.makeText(applicationContext, "Not implemented", Toast.LENGTH_LONG).show()
+ val intent = Intent(applicationContext, SetupConfigureSerpActivity::class.java)
+ intent.component =
+ ComponentName("com.porg.gugal", "com.porg.gugal.setup.SetupConfigureSerpActivity")
+ startActivity(intent)
}
) {
Text(getText(R.string.setup_p1_button).toString())
diff --git a/app/src/main/res/drawable/ic_back.xml b/app/src/main/res/drawable/ic_back.xml
new file mode 100644
index 0000000000000000000000000000000000000000..51f9b5d37dde904dbdaf04f6bd781cd6cec5993b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_back.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_donate.xml b/app/src/main/res/drawable/ic_donate.xml
index 1ce9c8924f77bb19539ba399e17045c7baac8e66..72f543978ea15233811b479e89d153cf33357de7 100644
--- a/app/src/main/res/drawable/ic_donate.xml
+++ b/app/src/main/res/drawable/ic_donate.xml
@@ -21,9 +21,8 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="?attr/colorOnPrimary">
+ android:viewportHeight="24">
diff --git a/app/src/main/res/drawable/ic_warning.xml b/app/src/main/res/drawable/ic_warning.xml
index 53bacef185c587c52c36465c522157ae6557f7c8..2c5ce4f11ca97e6f137a54db733ab74f41922eed 100644
--- a/app/src/main/res/drawable/ic_warning.xml
+++ b/app/src/main/res/drawable/ic_warning.xml
@@ -21,8 +21,7 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="?attr/colorOnPrimary">
+ android:viewportHeight="24">
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
index 5b6b1cc6794a1d2b702d02876a49b3b7c6851953..e3277dcdf280ce06e427e53fe7d65a3efc3b3fdc 100644
--- a/app/src/main/res/values-night/themes.xml
+++ b/app/src/main/res/values-night/themes.xml
@@ -1,16 +1,11 @@
-
\ 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 d9184284cbc39f7145bcd93f090d01c8b75f319b..f59be2e38e3f5f4411a13bcc6d87111753138b87 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -33,5 +33,8 @@
Select search engine
Set up search
Gugal is FOSS!
- Gugal is FOSS!
+ The source code is available for anyone to view and make edits at the GitLab repository. Official downloads for the app are only available at the GitLab releases page.
+ Only install Gugal from the official source. If you downloaded from any other source, it is highly recommended you uninstall it and reinstall from the official source. Tap for more info.
+ If you like Gugal and wish to make a donation, the link to do so is under the About section in the app\'s settings.
+ SetupFOSSActivity
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 77ee17590763a5f6e43340c21ca5f498fcb93c2e..88807dfce40ad8ff3c0387c0edf2781b1d5619fa 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,16 +1,11 @@
-
diff --git a/build.gradle b/build.gradle
index 69bf71c4ec571d65efe44f61b06ea4ed36584829..8c38b7a356ec4a4df02d11cbcd18052adcddef75 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,7 +9,7 @@ buildscript {
}
dependencies {
classpath "com.android.tools.build:gradle:7.0.4"
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10"
+ classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files