This commit is contained in:
Marvin W 2020-06-05 18:30:09 +02:00
parent 9a96cc4583
commit 3d37a285d4
No known key found for this signature in database
GPG Key ID: 072E9235DB996F2A
18 changed files with 1039 additions and 0 deletions

83
ui/build.gradle Normal file
View File

@ -0,0 +1,83 @@
/*
* SPDX-FileCopyrightText: 2019, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'maven-publish'
android {
compileSdkVersion androidCompileSdk
buildToolsVersion "$androidBuildVersionTools"
dataBinding {
enabled = true
}
defaultConfig {
versionName version
minSdkVersion Math.max(androidMinSdk, 14)
targetSdkVersion androidTargetSdk
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
}
dependencies {
implementation project(':api')
implementation project(':client')
implementation "androidx.appcompat:appcompat:$appcompatVersion"
implementation "androidx.fragment:fragment:$fragmentVersion"
implementation "androidx.recyclerview:recyclerview:$recyclerviewVersion"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
}
afterEvaluate {
publishing {
publications {
release(MavenPublication) {
pom {
name = 'UnifiedNlp UI'
description = 'UnifiedNlp UI library for common configuration fragments'
url = 'https://github.com/microg/UnifiedNlp'
licenses {
license {
name = 'The Apache Software License, Version 2.0'
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
developers {
developer {
id = 'microg'
name = 'microG Team'
}
developer {
id = 'mar-v-in'
name = 'Marvin W.'
}
}
scm {
url = 'https://github.com/microg/UnifiedNlp'
connection = 'scm:git:https://github.com/microg/UnifiedNlp.git'
developerConnection = 'scm:git:ssh://github.com/microg/UnifiedNlp.git'
}
}
from components.release
}
}
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2020, microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<manifest package="org.microg.nlp.ui">
<application />
</manifest>

View File

@ -0,0 +1,150 @@
package org.microg.nlp.ui
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.Intent.ACTION_VIEW
import android.content.pm.PackageManager.GET_META_DATA
import android.content.res.ColorStateList
import android.graphics.Color
import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat
import androidx.databinding.Observable
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import org.microg.nlp.client.UnifiedLocationClient
import org.microg.nlp.ui.BackendType.GEOCODER
import org.microg.nlp.ui.BackendType.LOCATION
import org.microg.nlp.ui.databinding.BackendDetailsBinding
import java.util.*
class BackendDetailsFragment : Fragment(R.layout.backend_details) {
fun Double.toStringWithDigits(digits: Int): String {
val s = this.toString()
val i = s.indexOf('.')
if (i <= 0 || s.length - i - 1 < digits) return s
if (digits == 0) return s.substring(0, i)
return s.substring(0, s.indexOf('.') + digits + 1)
}
fun Float.toStringWithDigits(digits: Int): String {
val s = this.toString()
val i = s.indexOf('.')
if (i <= 0 || s.length - i - 1 < digits) return s
if (digits == 0) return s.substring(0, i)
return s.substring(0, s.indexOf('.') + digits + 1)
}
@ColorInt
fun Context.resolveColor(@AttrRes resid: Int): Int? {
val typedValue = TypedValue()
if (!theme.resolveAttribute(resid, typedValue, true)) return null
val colorRes = if (typedValue.resourceId != 0) typedValue.resourceId else typedValue.data
return ContextCompat.getColor(this, colorRes)
}
val switchBarEnabledColor: Int
get() = context?.resolveColor(androidx.appcompat.R.attr.colorControlActivated) ?: Color.RED
val switchBarDisabledColor: Int
get() {
val color = context?.resolveColor(android.R.attr.textColorSecondary) ?: Color.RED
return Color.argb(100, Color.red(color), Color.green(color), Color.blue(color))
}
val switchBarTrackTintColor: ColorStateList
get() {
val color = context?.resolveColor(android.R.attr.textColorPrimaryInverse)
?: Color.RED
val withAlpha = Color.argb(50, Color.red(color), Color.green(color), Color.blue(color))
return ColorStateList(arrayOf(emptyArray<Int>().toIntArray()), arrayOf(withAlpha).toIntArray())
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val binding = BackendDetailsBinding.inflate(inflater, container, false)
binding.fragment = this
binding.switchWidget.trackTintList = switchBarTrackTintColor
lifecycleScope.launchWhenStarted {
val entry = createBackendInfo()
binding.entry = entry
binding.executePendingBindings()
if (entry?.type == LOCATION) {
val client = UnifiedLocationClient[entry.context]
val location = client.getLastLocationForBackend(entry.serviceInfo.packageName, entry.serviceInfo.name, entry.firstSignatureDigest)
?: return@launchWhenStarted
var locationString = "${location.latitude.toStringWithDigits(6)}, ${location.longitude.toStringWithDigits(6)}"
val address = client.getFromLocation(location.latitude, location.longitude, 1, Locale.getDefault().toString()).singleOrNull()
if (address != null) {
val addressLine = StringBuilder()
var i = 0
addressLine.append(address.getAddressLine(i))
while (addressLine.length < 10 && address.maxAddressLineIndex > i) {
i++
addressLine.append(", ")
addressLine.append(address.getAddressLine(i))
}
locationString = addressLine.toString()
}
binding.lastLocationString = locationString
binding.executePendingBindings()
}
}
return binding.root
}
fun onBackendEnabledChanged(entry: BackendInfo) {
entry.enabled = !entry.enabled
}
private fun createExternalIntent(packageName: String, activityName: String): Intent {
val intent = Intent(ACTION_VIEW);
intent.setPackage(packageName);
intent.setClassName(packageName, activityName);
return intent;
}
private fun startExternalActivity(packageName: String, activityName: String) {
requireContext().startActivity(createExternalIntent(packageName, activityName))
}
fun onAboutClicked(entry: BackendInfo) {
entry.aboutActivity?.let { activityName -> startExternalActivity(entry.serviceInfo.packageName, activityName) }
}
fun onConfigureClicked(entry: BackendInfo) {
entry.settingsActivity?.let { activityName -> startExternalActivity(entry.serviceInfo.packageName, activityName) }
}
private suspend fun createBackendInfo(): BackendInfo? {
val activity = activity ?: return null
val intent = activity.intent ?: return null
val type = BackendType.values().find { it.name == intent.getStringExtra(EXTRA_TYPE) }
?: return null
val packageName = intent.getStringExtra(EXTRA_PACKAGE) ?: return null
val name = intent.getStringExtra(EXTRA_NAME) ?: return null
val serviceInfo = activity.packageManager.getServiceInfo(ComponentName(packageName, name), GET_META_DATA)
?: return null
val enabledBackends = when (type) {
GEOCODER -> UnifiedLocationClient[activity].getGeocoderBackends()
LOCATION -> UnifiedLocationClient[activity].getLocationBackends()
}
return BackendInfo(activity, serviceInfo, type, lifecycleScope, enabledBackends)
}
companion object {
val ACTION = "org.microg.nlp.ui.BACKEND_DETAILS"
val EXTRA_TYPE = "org.microg.nlp.ui.BackendDetailsFragment.type"
val EXTRA_PACKAGE = "org.microg.nlp.ui.BackendDetailsFragment.package"
val EXTRA_NAME = "org.microg.nlp.ui.BackendDetailsFragment.name"
}
}

View File

@ -0,0 +1,95 @@
package org.microg.nlp.ui
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.ServiceInfo
import android.graphics.drawable.Drawable
import android.util.Log
import androidx.databinding.BaseObservable
import androidx.databinding.Bindable
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.microg.nlp.api.Constants
import org.microg.nlp.client.UnifiedLocationClient
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
private val TAG: String = "ULocUI"
class BackendInfo(val context: Context, val serviceInfo: ServiceInfo, val type: BackendType, val coroutineScope: CoroutineScope, enabledBackends: Array<String>) : BaseObservable() {
val firstSignatureDigest = firstSignatureDigest(context, serviceInfo.packageName)
val unsignedComponent: String = "${serviceInfo.packageName}/${serviceInfo.name}"
val signedComponent: String = "${serviceInfo.packageName}/${serviceInfo.name}/$firstSignatureDigest"
var enabled: Boolean = enabledBackends.contains(signedComponent) || enabledBackends.contains(unsignedComponent)
@Bindable get
set(value) {
if (field == value) return
field = value
notifyPropertyChanged(BR.enabled)
coroutineScope.launch {
val client = UnifiedLocationClient[context]
val withoutSelf = when (type) {
BackendType.LOCATION -> client.getLocationBackends()
BackendType.GEOCODER -> client.getGeocoderBackends()
}.filterNot { it == unsignedComponent || it.startsWith("$unsignedComponent/") }.toTypedArray()
val new = if (value) withoutSelf + signedComponent else withoutSelf
try {
when (type) {
BackendType.LOCATION -> client.setLocationBackends(new)
BackendType.GEOCODER -> client.setGeocoderBackends(new)
}
} catch (e: Exception) {
Log.w(TAG, "Failed to change backend state", e)
field = !value
notifyPropertyChanged(BR.enabled)
}
}
}
val appIcon: Drawable by lazy { serviceInfo.loadIcon(context.packageManager) }
val name: CharSequence by lazy { serviceInfo.loadLabel(context.packageManager) }
val appName: CharSequence by lazy { serviceInfo.applicationInfo.loadLabel(context.packageManager) }
val backendSummary: String? by lazy { serviceInfo.metaData?.getString(Constants.METADATA_BACKEND_SUMMARY) }
val settingsActivity: String? by lazy { serviceInfo.metaData?.getString(Constants.METADATA_BACKEND_SETTINGS_ACTIVITY) }
val aboutActivity: String? by lazy { serviceInfo.metaData?.getString(Constants.METADATA_BACKEND_ABOUT_ACTIVITY) }
}
enum class BackendType { LOCATION, GEOCODER }
@Suppress("DEPRECATION")
@SuppressLint("PackageManagerGetSignatures")
fun firstSignatureDigest(context: Context, packageName: String?): String? {
val packageManager = context.packageManager
val info: PackageInfo?
try {
info = packageManager.getPackageInfo(packageName!!, PackageManager.GET_SIGNATURES)
} catch (e: PackageManager.NameNotFoundException) {
return null
}
if (info?.signatures?.isNotEmpty() == true) {
for (sig in info.signatures) {
sha256sum(sig.toByteArray())?.let { return it }
}
}
return null
}
private fun sha256sum(bytes: ByteArray): String? {
try {
val md = MessageDigest.getInstance("SHA-256")
val digest = md.digest(bytes)
val sb = StringBuilder(2 * digest.size)
for (b in digest) {
sb.append(String.format("%02x", b))
}
return sb.toString()
} catch (e: NoSuchAlgorithmException) {
return null
}
}

View File

@ -0,0 +1,86 @@
package org.microg.nlp.ui
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageManager.GET_META_DATA
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import org.microg.nlp.api.Constants.*
import org.microg.nlp.client.UnifiedLocationClient
import org.microg.nlp.ui.BackendDetailsFragment.Companion.EXTRA_NAME
import org.microg.nlp.ui.BackendDetailsFragment.Companion.EXTRA_PACKAGE
import org.microg.nlp.ui.BackendDetailsFragment.Companion.EXTRA_TYPE
import org.microg.nlp.ui.databinding.BackendListBinding
import org.microg.nlp.ui.databinding.BackendListEntryBinding
class BackendListFragment : Fragment(R.layout.backend_list) {
val locationAdapter: BackendSettingsLineAdapter = BackendSettingsLineAdapter(this)
val geocoderAdapter: BackendSettingsLineAdapter = BackendSettingsLineAdapter(this)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val binding = BackendListBinding.inflate(inflater, container, false)
binding.fragment = this
lifecycleScope.launchWhenStarted { updateAdapters() }
return binding.root
}
fun onBackendSelected(entry: BackendInfo) {
val intent = Intent(BackendDetailsFragment.ACTION)
//intent.`package` = requireContext().packageName
intent.putExtra(EXTRA_TYPE, entry.type.name)
intent.putExtra(EXTRA_PACKAGE, entry.serviceInfo.packageName)
intent.putExtra(EXTRA_NAME, entry.serviceInfo.name)
context?.packageManager?.queryIntentActivities(intent, 0)?.forEach {
Log.d("USettings", it.activityInfo.name)
}
startActivity(intent)
}
private suspend fun updateAdapters() {
val context = requireContext()
locationAdapter.entries = createBackendInfoList(context, Intent(ACTION_LOCATION_BACKEND), UnifiedLocationClient[context].getLocationBackends(), BackendType.LOCATION)
geocoderAdapter.entries = createBackendInfoList(context, Intent(ACTION_GEOCODER_BACKEND), UnifiedLocationClient[context].getGeocoderBackends(), BackendType.GEOCODER)
}
private fun createBackendInfoList(context: Context, intent: Intent, enabledBackends: Array<String>, type: BackendType): Array<BackendInfo> {
val backends = context.packageManager.queryIntentServices(intent, GET_META_DATA).map { BackendInfo(context, it.serviceInfo, type, lifecycleScope, enabledBackends) }
return backends.toTypedArray()
}
}
class BackendSettingsLineViewHolder(val binding: BackendListEntryBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(fragment: BackendListFragment, entry: BackendInfo) {
binding.fragment = fragment
binding.entry = entry
binding.executePendingBindings()
}
}
class BackendSettingsLineAdapter(val fragment: BackendListFragment) : RecyclerView.Adapter<BackendSettingsLineViewHolder>() {
var entries: Array<BackendInfo> = emptyArray()
set(value) {
field = value
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BackendSettingsLineViewHolder {
return BackendSettingsLineViewHolder(BackendListEntryBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: BackendSettingsLineViewHolder, position: Int) {
holder.bind(fragment, entries[position])
}
override fun getItemCount(): Int {
return entries.size
}
}

View File

@ -0,0 +1,301 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2020, microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<import type="org.microg.nlp.ui.BackendType" />
<variable
name="fragment"
type="org.microg.nlp.ui.BackendDetailsFragment" />
<variable
name="entry"
type="org.microg.nlp.ui.BackendInfo" />
<variable
name="lastLocationString"
type="String" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:paddingStart="?attr/listPreferredItemPaddingStart"
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
android:paddingTop="24dp"
android:paddingEnd="?attr/listPreferredItemPaddingEnd"
android:paddingRight="?attr/listPreferredItemPaddingRight"
android:paddingBottom="16dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:gravity="center_horizontal"
android:orientation="vertical">
<ImageView
android:id="@+id/icon"
android:layout_width="48dp"
android:layout_height="48dp"
android:antialias="true"
android:scaleType="fitCenter"
android:src="@{entry.appIcon}"
tools:src="@android:mipmap/sym_def_app_icon" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ellipsize="marquee"
android:gravity="center"
android:singleLine="false"
android:text='@{entry.name}'
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
android:textColor="?android:attr/textColorPrimary"
android:textSize="20sp"
tools:text="Mozilla Location Service" />
<TextView
android:id="@+id/app_name"
style="@style/TextAppearance.AppCompat.Body1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text='@{entry.appName ?? ""}'
android:textColor="?android:attr/textColorSecondary"
android:visibility='@{entry != null &amp;&amp; entry.appName == entry.name ? View.GONE : View.VISIBLE}'
tools:text="Mozilla UnifiedNlp Backend" />
</LinearLayout>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:onClick="@{() -> fragment.onBackendEnabledChanged(entry)}"
android:orientation="horizontal"
android:paddingStart="?attr/listPreferredItemPaddingStart"
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
android:paddingEnd="?attr/listPreferredItemPaddingEnd"
android:paddingRight="?attr/listPreferredItemPaddingRight"
app:backgroundColor="@{entry.enabled ? fragment.switchBarEnabledColor : fragment.switchBarDisabledColor}"
tools:background="?attr/colorAccent">
<TextView
android:id="@+id/switch_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="2"
android:paddingStart="56dp"
android:paddingLeft="56dp"
android:text="Use backend"
android:textAppearance="?attr/textAppearanceListItem"
android:textColor="?android:attr/textColorPrimaryInverse" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_widget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="@null"
android:checked="@={entry.enabled}"
app:thumbTint="?android:attr/textColorPrimaryInverse"
tools:checked="true" />
</LinearLayout>
<LinearLayout
android:id="@+id/settingsLauncher"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:clipToPadding="false"
android:focusable="true"
android:gravity="start|center_vertical"
android:minHeight="?attr/listPreferredItemHeightSmall"
android:onClick="@{() -> fragment.onConfigureClicked(entry)}"
android:paddingStart="?attr/listPreferredItemPaddingStart"
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
android:paddingEnd="?attr/listPreferredItemPaddingEnd"
android:paddingRight="?attr/listPreferredItemPaddingRight"
android:visibility='@{entry == null || entry.settingsActivity == null ? View.GONE : View.VISIBLE}'>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="56dp"
android:paddingLeft="56dp"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:singleLine="true"
android:text="Configure"
android:textAppearance="?attr/textAppearanceListItem" />
</RelativeLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/aboutLauncher"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:clipToPadding="false"
android:focusable="true"
android:gravity="start|center_vertical"
android:minHeight="?attr/listPreferredItemHeightSmall"
android:onClick="@{() -> fragment.onAboutClicked(entry)}"
android:paddingStart="?attr/listPreferredItemPaddingStart"
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
android:paddingEnd="?attr/listPreferredItemPaddingEnd"
android:paddingRight="?attr/listPreferredItemPaddingRight"
android:visibility='@{entry == null || entry.aboutActivity == null ? View.GONE : View.VISIBLE}'>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="56dp"
android:paddingLeft="56dp"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<TextView
android:id="@+id/about_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:singleLine="true"
android:text='About'
android:textAppearance="?attr/textAppearanceListItem" />
</RelativeLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/dividerHorizontal"
android:visibility='@{entry == null || (entry.backendSummary == null || entry.type != BackendType.LOCATION) || (entry.settingsActivity == null &amp;&amp; entry.aboutActivity == null) ? View.GONE : View.VISIBLE}' />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:gravity="start|center_vertical"
android:minHeight="?attr/listPreferredItemHeightSmall"
android:paddingStart="?attr/listPreferredItemPaddingStart"
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
android:paddingEnd="?attr/listPreferredItemPaddingEnd"
android:paddingRight="?attr/listPreferredItemPaddingRight"
android:visibility='@{entry == null || entry.backendSummary == null ? View.GONE : View.VISIBLE}'>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="56dp"
android:paddingLeft="56dp"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<TextView
android:id="@+id/description_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:singleLine="true"
android:text="Description"
android:textAppearance="?attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary" />
<TextView
android:id="@+id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/description_title"
android:layout_alignStart="@id/description_title"
android:layout_alignLeft="@id/description_title"
android:maxLines="10"
android:text='@{entry.backendSummary ?? ""}'
android:textAppearance="?attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
tools:text="Locate using Mozilla\'s online database" />
</RelativeLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:gravity="start|center_vertical"
android:minHeight="?attr/listPreferredItemHeightSmall"
android:paddingStart="?attr/listPreferredItemPaddingStart"
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
android:paddingEnd="?attr/listPreferredItemPaddingEnd"
android:paddingRight="?attr/listPreferredItemPaddingRight"
android:visibility='@{lastLocationString == null ? View.GONE : View.VISIBLE}'>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="56dp"
android:paddingLeft="56dp"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<TextView
android:id="@+id/last_location_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:singleLine="true"
android:text="Last location"
android:textAppearance="?attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary" />
<TextView
android:id="@+id/last_location"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/last_location_title"
android:layout_alignStart="@id/last_location_title"
android:layout_alignLeft="@id/last_location_title"
android:maxLines="10"
android:text='@{lastLocationString ?? ""}'
android:textAppearance="?attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
tools:text="Main St 23" />
</RelativeLayout>
</LinearLayout>
</LinearLayout>
</layout>

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2020, microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="fragment"
type="org.microg.nlp.ui.BackendListFragment" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:clipToPadding="false"
android:gravity="start|center_vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingRight="?android:attr/listPreferredItemPaddingRight">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:letterSpacing="0.073"
android:paddingStart="56dp"
android:paddingLeft="56dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:singleLine="true"
android:text="@string/network_location"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textColor="?attr/colorAccent"
android:textSize="11sp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/network_location_backend_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:adapter="@{fragment.locationAdapter}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="2"
tools:listitem="@layout/backend_list_entry" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/dividerHorizontal" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:clipToPadding="false"
android:gravity="start|center_vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingRight="?android:attr/listPreferredItemPaddingRight">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:paddingStart="56dp"
android:paddingLeft="56dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:singleLine="true"
android:text="@string/geocoding"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textColor="?attr/colorAccent"
android:textSize="11sp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/geocoding_backend_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:adapter="@{fragment.geocoderAdapter}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="1"
tools:listitem="@layout/backend_list_entry" />
</LinearLayout>
</layout>

View File

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2020, microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="fragment"
type="org.microg.nlp.ui.BackendListFragment" />
<variable
name="entry"
type="org.microg.nlp.ui.BackendInfo" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:clipToPadding="false"
android:focusable="true"
android:gravity="start|center_vertical"
android:onClick="@{() -> fragment.onBackendSelected(entry)}"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingRight="?android:attr/listPreferredItemPaddingRight">
<LinearLayout
android:id="@+id/icon_frame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:gravity="start|center_vertical"
android:minWidth="56dp"
android:orientation="horizontal"
android:paddingTop="4dp"
android:paddingBottom="4dp">
<ImageView
android:id="@+id/icon"
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@{entry.appIcon}"
tools:src="@android:mipmap/sym_def_app_icon" />
</LinearLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:singleLine="true"
android:text='@{entry.name}'
android:textAppearance="?android:attr/textAppearanceListItem"
tools:text="Mozilla Location Service" />
</RelativeLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/two_target_divider"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="start|center_vertical"
android:orientation="horizontal"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="?android:attr/listDivider" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:minWidth="64dp"
android:orientation="vertical">
<androidx.appcompat.widget.SwitchCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:checked="@={entry.enabled}"
android:minWidth="80dp"
android:paddingLeft="16dp"
android:paddingRight="16dp" />
</LinearLayout>
</LinearLayout>
</layout>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2014, microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<resources>
<string name="network_location">Funknetz-basierte Ortung</string>
<string name="geocoding">Adressauflösung</string>
</resources>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2014, microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<resources>
<string name="network_location">Ret-bazita pozici-trovado</string>
<string name="geocoding">Adres-elserĉado</string>
</resources>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2014, microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<resources>
<string name="network_location">Géolocalisation réseau</string>
<string name="geocoding">Géocodage</string>
</resources>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2014, microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<resources>
<string name="network_location">Geolokalizacja oparta o sieć</string>
<string name="geocoding">Wyszukiwanie adresów</string>
</resources>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2014, microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<resources>
<string name="network_location">Localizare geografică bazată pe rețea</string>
<string name="geocoding">Căutare adrese</string>
</resources>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2014, microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<resources>
<string name="network_location">Местоположение по сети</string>
<string name="geocoding">Поиск адреса</string>
</resources>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2014, microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<resources>
<string name="network_location">Мрежна геолокација</string>
<string name="geocoding">Потрага адресе</string>
</resources>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2014, microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<resources>
<string name="network_location">Географічне позиціювання на основі мереж</string>
<string name="geocoding">Пошук адрес</string>
</resources>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2014, microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<resources>
<string name="network_location">基於網路的地理位置定位</string>
<string name="geocoding">地址查閱</string>
</resources>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2014, microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<resources>
<string name="network_location">Network-based Geolocation</string>
<string name="geocoding">Address lookup</string>
</resources>