Commit 3ae07c0e authored by Dayona Joseph's avatar Dayona Joseph Committed by Arnau Vàzquez
Browse files

Support language translation

parent f03a44a6
......@@ -69,4 +69,4 @@
android:theme="@style/AppTheme1"/>
</application>
</manifest>
</manifest>
\ No newline at end of file
......@@ -18,8 +18,9 @@
package foundation.e.apps
//import androidx.fragment.app.ListFragment
import android.annotation.SuppressLint
import android.content.SharedPreferences
import android.content.*
import android.content.pm.PackageManager
import android.os.Bundle
import android.os.Handler
......@@ -27,6 +28,7 @@ import android.preference.PreferenceManager
import android.view.MenuItem
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import com.google.android.material.bottomnavigation.BottomNavigationItemView
import com.google.android.material.bottomnavigation.BottomNavigationMenuView
......@@ -83,6 +85,7 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
return@setOnNavigationItemSelectedListener false
}
disableShiftingOfNabBarItems()
initialiseUpdatesWorker()
......@@ -97,7 +100,7 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
} else {
R.id.menu_home
}
setupLangReceiver()
applicationManagerServiceConnection.bindService(this)
}
......@@ -172,13 +175,27 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
}
return false
}
private var mLangReceiver: BroadcastReceiver? = null
protected fun setupLangReceiver(): BroadcastReceiver? {
if (mLangReceiver == null) {
mLangReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
finish()
// selectFragment(currentFragmentId,null)
}
}
val filter = IntentFilter(Intent.ACTION_LOCALE_CHANGED)
registerReceiver(mLangReceiver, filter)
}
return mLangReceiver
}
private fun showFragment(fragment: Fragment) {
bottom_navigation_view.menu.findItem(currentFragmentId).isChecked = true
supportFragmentManager
.beginTransaction()
.replace(R.id.frame_layout, fragment)
.commit()
.commitAllowingStateLoss();
}
@SuppressLint("RestrictedApi")
......
......@@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonProperty
import foundation.e.apps.MainActivity
import foundation.e.apps.application.model.Application
import foundation.e.apps.application.model.data.BasicData
import foundation.e.apps.application.model.data.PwasBasicData
import foundation.e.apps.applicationmanager.ApplicationManager
import foundation.e.apps.categories.model.Category
......@@ -18,15 +19,15 @@ import foundation.e.apps.utils.Error
class HomePwaRequest {
companion object {
private val reader = Common.getObjectMapper().readerFor(HomeResult::class.java)
private val mapper = Common.getObjectMapper()
}
fun request(callback: (Error?, HomeResult?) -> Unit) {
fun request(callback: (Error?, Result?) -> Unit) {
try {
var appType = MainActivity.mActivity.showApplicationTypePreference()
val url = Constants.BASE_URL + "apps?action=list_home&type=$appType"
val urlConnection = Common.createConnection(url, Constants.REQUEST_METHOD_GET)
val result = reader.readValue<HomeResult>(urlConnection.inputStream)
val result = mapper.readValue(urlConnection.inputStream, Result::class.java)
urlConnection.disconnect()
callback.invoke(null, result)
} catch (e: Exception) {
......@@ -34,56 +35,50 @@ class HomePwaRequest {
}
}
class HomeResult @JsonCreator
constructor(@JsonProperty("home") private val home: PwasSubHomeResult) {
data class Result(val success: Boolean, val home: Home)
data class Home(
@JsonProperty("headings")
val headings: Map<String, String>?,
@JsonProperty(BANNER_APPS_KEY)
val bannerApps: List<BasicData>,
@JsonProperty(POPULAR_APPS_KEY)
val topUpdatedApps: List<BasicData>,
@JsonProperty(POPULAR_GAMES_KEY)
val topUpdatedGames: List<BasicData>,
@JsonProperty(DISCOVER_KEY)
val discover: List<BasicData>
) {
companion object {
private const val BANNER_APPS_KEY = "banner_apps"
private const val POPULAR_APPS_KEY = "popular_apps"
private const val POPULAR_GAMES_KEY = "popular_games"
private const val DISCOVER_KEY = "discover"
private val KEYS = setOf(POPULAR_APPS_KEY,
POPULAR_GAMES_KEY, DISCOVER_KEY)
}
fun getBannerApps(applicationManager: ApplicationManager, context: Context): ArrayList<Application> {
return ApplicationParser.PwaParseToApps(applicationManager, context, home.bannerApps)
return ApplicationParser.parseToApps(applicationManager, context, bannerApps.toTypedArray())
}
fun getApps(applicationManager: ApplicationManager, context: Context): LinkedHashMap<Category, ArrayList<Application>> {
val apps = LinkedHashMap<Category, ArrayList<Application>>()
for (pair in home.apps) {
if(pair.value .isEmpty() ){
apps.remove(pair.key)
}else {
apps[pair.key] = ApplicationParser.PwaParseToApps(applicationManager, context, pair.value.toTypedArray())
KEYS.forEach {
var heading = headings?.get(it)
heading = heading
?: "" // Use default heading as empty to let it generate from the key itself.
val parsedApps = when (it) {
POPULAR_APPS_KEY -> ApplicationParser.parseToApps(applicationManager, context, topUpdatedApps.toTypedArray())
POPULAR_GAMES_KEY -> ApplicationParser.parseToApps(applicationManager, context, topUpdatedGames.toTypedArray())
DISCOVER_KEY -> ApplicationParser.parseToApps(applicationManager, context, discover.toTypedArray())
else -> throw IllegalArgumentException("Unrecognised key $it encountered")
}
apps[Category(it, heading)] = parsedApps
}
return apps
}
}
class PwasSubHomeResult @JsonCreator constructor() {
@JsonIgnore
val apps = LinkedHashMap<Category, ArrayList<PwasBasicData>>()
lateinit var bannerApps: Array<PwasBasicData>
@JsonAnySetter
fun append(key: String, value: Any) {
val apps = value as ArrayList<*>
val appsData = ArrayList<PwasBasicData>()
apps.forEach {
val data = it as LinkedHashMap<*, *>
val appData = PwasBasicData(
data["_id"] as String,
data["name"] as String,
data["description"] as String?,
data["is_pwa"] as Boolean,
data["is_web_app"] as Boolean,
data["has_https"] as Boolean,
data["url"] as String?,
data["category"] as String,
data["icon_image_path"] as String,
(data["other_images_path"]as List<String>).toTypedArray(),
data["created_on"] as String?)
appsData.add(appData)
}
if (key == "banner_apps") {
bannerApps = appsData.toTypedArray()
} else {
this.apps[Category(key)] = appsData
}
}
}
}
\ No newline at end of file
}
......@@ -54,6 +54,8 @@ class HomeRequest {
data class Result(val success: Boolean, val home: Home)
data class Home(
@JsonProperty("headings")
val headings: Map<String, String>?,
@JsonProperty(BANNER_APPS_KEY)
val bannerApps: List<BasicData>,
@JsonProperty(TOP_UPDATED_APPS_KEY)
......@@ -80,15 +82,16 @@ class HomeRequest {
POPULAR_GAMES_24_HOUR_KEY, DISCOVER_KEY)
}
fun getBannerApps(applicationManager: ApplicationManager, context: Context): ArrayList<Application> {
return ApplicationParser.parseToApps(applicationManager, context, bannerApps.toTypedArray())
}
fun getApps(applicationManager: ApplicationManager, context: Context): LinkedHashMap<Category, ArrayList<Application>> {
val apps = LinkedHashMap<Category, ArrayList<Application>>()
KEYS.forEach {
var heading = headings?.get(it)
heading = heading
?: "" // Use default heading as empty to let it generate from the key itself.
val parsedApps = when (it) {
TOP_UPDATED_APPS_KEY -> ApplicationParser.parseToApps(applicationManager, context, topUpdatedApps.toTypedArray())
TOP_UPDATED_GAMES_KEY -> ApplicationParser.parseToApps(applicationManager, context, topUpdatedGames.toTypedArray())
......@@ -97,11 +100,9 @@ class HomeRequest {
DISCOVER_KEY -> ApplicationParser.parseToApps(applicationManager, context, discover.toTypedArray())
else -> throw IllegalArgumentException("Unrecognised key $it encountered")
}
apps[Category(it)] = parsedApps
apps[Category(it, heading)] = parsedApps
}
return apps
}
}
}
......@@ -20,14 +20,16 @@ package foundation.e.apps.api
import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty
import foundation.e.apps.MainActivity.Companion.mActivity
import foundation.e.apps.categories.model.Category
import foundation.e.apps.utils.Common
import foundation.e.apps.utils.Constants
import foundation.e.apps.utils.Error
import java.io.Serializable
class ListCategoriesRequest {
companion object {
private val reader = Common.getObjectMapper().readerFor(ListCategoriesResult::class.java)
private val mapper = Common.getObjectMapper()
}
fun request(callback: (Error?, ListCategoriesResult?) -> Unit) {
......@@ -35,15 +37,37 @@ class ListCategoriesRequest {
var appType = mActivity.showApplicationTypePreference()
val url = Constants.BASE_URL + "apps?action=list_cat&type=$appType"
val urlConnection = Common.createConnection(url, Constants.REQUEST_METHOD_GET)
val result = reader.readValue<ListCategoriesResult>(urlConnection.inputStream)
val result = mapper.readValue(urlConnection.inputStream, ListCategoriesResult::class.java)
urlConnection.disconnect()
callback.invoke(null, result)
} catch (e: Exception) {
e.printStackTrace()
callback.invoke(Error.findError(e), null)
}
}
class ListCategoriesResult @JsonCreator
constructor(@JsonProperty("apps") val appsCategories: Array<String>,
@JsonProperty("games") val gamesCategories: Array<String>)
@JsonProperty("games") val gamesCategories: Array<String>,
@JsonProperty ("translations") val translations : Map <String,String>) : Serializable {
fun appsParseResult(): ArrayList<Category> {
val appCategories = ArrayList<Category>()
appsCategories.forEach { id ->
val translations = translations[id]
appCategories.add(Category(id, translations!!))
}
return appCategories
}
fun gameParseResult(): ArrayList<Category> {
val gameCategories = ArrayList<Category>()
gamesCategories.forEach { id ->
val translations = translations[id]
gameCategories.add(Category(id, translations!!))
}
return gameCategories
}
}
}
......@@ -86,7 +86,7 @@ constructor(
val category: Category
init {
this.category = Category(categoryId)
this.category = Category(categoryId, "")
}
@Suppress("unused")
......
......@@ -25,7 +25,7 @@ constructor(
val category: Category
init {
this.category = Category(categoryId)
this.category = Category(categoryId, "")
}
}
......
......@@ -69,15 +69,9 @@ class CategoriesModel : CategoriesModelInterface {
}
private fun parseResult(result: ListCategoriesRequest.ListCategoriesResult) {
val appsCategories = ArrayList<Category>()
val gamesCategories = ArrayList<Category>()
result.appsCategories.forEach { id ->
appsCategories.add(Category(id))
}
result.gamesCategories.forEach { id ->
gamesCategories.add(Category(id))
}
applicationsCategoriesList.value = appsCategories
gamesCategoriesList.value = gamesCategories
val apps=result.appsParseResult()
val games=result.gameParseResult()
applicationsCategoriesList.value = apps
gamesCategoriesList.value = games
}
}
......@@ -20,7 +20,7 @@ package foundation.e.apps.categories.model
import foundation.e.apps.R
import java.io.Serializable
class Category(val id: String, val defaultTitle: String? = "") : Serializable {
class Category(val id: String, val result: String = "") : Serializable {
private val title: String
private val iconResource: Int
......@@ -31,15 +31,12 @@ class Category(val id: String, val defaultTitle: String? = "") : Serializable {
}
private fun getCategoryTitle(categoryId: String): String {
if(defaultTitle!!.isNotEmpty()) return defaultTitle.toString()
if(result.isNotEmpty()) return result
val title = categoryId.replace("_", " ")
if (title.contains("game ")) {
return title.removePrefix("game ").capitalize()
}else if(title.contains("pwa ")){
return title.removePrefix("pwa ").capitalize()
else{
val title = categoryId.replace("_", " ")
return title.capitalize()
}
return title.capitalize()
}
private fun getCategoryIconResource(categoryId: String): Int {
......@@ -199,3 +196,4 @@ class Category(val id: String, val defaultTitle: String? = "") : Serializable {
return iconResource
}
}
......@@ -43,8 +43,8 @@ class ApplicationsLoader(private val homeModel: HomeModel) : AsyncTask<Context,
HomePwaRequest().request{applicationError,pwaHomeResult ->
when (applicationError) {
null -> {
bannerApps = pwaHomeResult!!.getBannerApps(homeModel.getInstallManager(), context)
applications = pwaLoadApplications(pwaHomeResult, context)
bannerApps = pwaHomeResult!!.home.getBannerApps(homeModel.getInstallManager(), context)
applications = pwaLoadApplications(pwaHomeResult.home, context)
}
else -> {
error = applicationError
......@@ -59,6 +59,7 @@ class ApplicationsLoader(private val homeModel: HomeModel) : AsyncTask<Context,
homeResult!!
bannerApps = homeResult.home.getBannerApps(homeModel.getInstallManager(), context)
applications = loadApplications(homeResult.home, context)
}
else -> {
error = applicationError
......@@ -82,12 +83,9 @@ class ApplicationsLoader(private val homeModel: HomeModel) : AsyncTask<Context,
return home.getApps(homeModel.getInstallManager(), context)
}
private fun pwaLoadApplications(result: HomePwaRequest.HomeResult, context: Context): LinkedHashMap<Category, ArrayList<Application>> {
val parsedApplications = result.getApps(homeModel.getInstallManager(), context)
val applications = LinkedHashMap<Category, ArrayList<Application>>()
for (parsedApplication in parsedApplications) {
applications[parsedApplication.key] = parsedApplication.value
}
return applications
private fun pwaLoadApplications(home: HomePwaRequest.Home, context: Context): LinkedHashMap<Category, ArrayList<Application>> {
return home.getApps(homeModel.getInstallManager(), context)
}
}
......@@ -21,9 +21,19 @@ import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.net.ConnectivityManager
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.os.LocaleListCompat
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.KeyDeserializer
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import foundation.e.apps.categories.model.Category
import java.net.URL
import java.nio.file.Paths
import java.util.*
import java.util.concurrent.Executors
import javax.net.ssl.HttpsURLConnection
import kotlin.math.roundToInt
......@@ -46,7 +56,9 @@ object Common {
}
fun createConnection(url: String, requestMethod: String): HttpsURLConnection {
val preferredLanguage =getAcceptedLanguageHeaderValue()
val connection = URL(url).openConnection() as HttpsURLConnection
connection.setRequestProperty("Accept-Language", preferredLanguage)
connection.requestMethod = requestMethod
connection.connectTimeout = Constants.CONNECT_TIMEOUT
connection.readTimeout = Constants.READ_TIMEOUT
......@@ -86,8 +98,40 @@ object Common {
fun getObjectMapper(): ObjectMapper {
val objectMapper = ObjectMapper()
var simpleModule = SimpleModule()
simpleModule.addKeyDeserializer(Category::class.java,keyDeserializer())
objectMapper.registerModule(simpleModule);
objectMapper.registerKotlinModule()
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
objectMapper.registerKotlinModule()
return objectMapper
}
fun getAcceptedLanguageHeaderValue(): String {
var weight = 1.0F
return getPreferredLocaleList()
.map { it.toLanguageTag() }
.reduce { accumulator, languageTag ->
weight -= 0.1F
"$accumulator,$languageTag;q=$weight"
}
}
fun getPreferredLocaleList(): List<Locale> {
val adjustedLocaleListCompat = LocaleListCompat.getAdjustedDefault()
val preferredLocaleList = mutableListOf<Locale>()
for (index in 0 until adjustedLocaleListCompat.size()) {
preferredLocaleList.add(adjustedLocaleListCompat.get(index))
}
return preferredLocaleList
}
}
class keyDeserializer : KeyDeserializer() {
@RequiresApi(Build.VERSION_CODES.O)
override fun deserializeKey(p0: String?, p1: DeserializationContext?): Any? {
return Paths.get(p0)
}
}
......@@ -150,7 +150,7 @@
<RelativeLayout
android:id="@+id/app_install_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="30dp"
android:layout_marginTop="@dimen/layout_margin_large"
android:paddingStart="@dimen/layout_padding_large"
android:paddingEnd="@dimen/layout_padding_large">
......@@ -211,11 +211,12 @@
<TextView
android:id="@+id/app_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_marginStart="@dimen/layout_margin_medium"
android:layout_toStartOf="@id/app_install"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:textSize="@dimen/text_size_small"
tools:text="App size" />
......@@ -226,12 +227,13 @@
android:layout_width="@dimen/install_button_width3"
android:layout_height="@dimen/install_button_height"
android:layout_alignParentEnd="true"
android:ellipsize="end"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/layout_margin_medium2"
android:layout_marginTop="@dimen/layout_margin_small"
layout="@layout/install_button_layout"
layout="@layout/install_button_layout" />
/>
</RelativeLayout>
......@@ -828,3 +830,4 @@
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
......@@ -137,7 +137,7 @@
android:orientation="vertical">
<include
android:layout_width="@dimen/install_button_width2"
android:layout_width="@dimen/install_button_width3"
android:layout_height="@dimen/install_button_height"
android:layout_marginTop="@dimen/layout_margin_medium2"
layout="@layout/simple_install_button_layout"
......
......@@ -16,23 +16,19 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/app_install"
android:layout_width="wrap_content"
android:layout_height="@dimen/install_button_height2"
android:layout_gravity="center"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:background="@drawable/app_install_border"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:minWidth="@dimen/install_button_width"
android:minHeight="@dimen/install_button_height3"
android:text="@string/action_install"
android:textColor="?android:textColorPrimaryInverse"
android:textAllCaps="false"
android:textAlignment="center"
android:textSize="@dimen/text_size_large"
/>
android:textAllCaps="false"
android:textColor="?android:textColorPrimaryInverse"
android:layout_centerVertical="true"
android:textSize="@dimen/text_size_large"/>
......@@ -18,6 +18,7 @@
-->
<Button xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/app_install"
android:layout_width="wrap_content"
android:layout_height="@dimen/install_button_height2"
......@@ -32,6 +33,7 @@
android:textColor="@color/colorInstall"
android:textSize="@dimen/text_size_large"
android:textAllCaps="false"
android:gravity="top"
/>
<!-- android:padding="@dimen/layout_padding_medium"-->
android:gravity="center"
app:autoSizeMaxTextSize="18sp"
app:autoSizeMinTextSize="8sp"
app:autoSizeTextType="uniform" />
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment