Report creation code without much UI

dev
Laurent 7 years ago
parent 185c459066
commit e83914815d
  1. 5
      app/src/main/AndroidManifest.xml
  2. 141
      app/src/main/java/net/pokeranalytics/android/model/Criteria.kt
  3. 14
      app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt
  4. 11
      app/src/main/java/net/pokeranalytics/android/model/realm/ReportSetup.kt
  5. 24
      app/src/main/java/net/pokeranalytics/android/ui/activity/ReportCreationActivity.kt
  6. 102
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ReportCreationFragment.kt
  7. 9
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ReportsFragment.kt
  8. 15
      app/src/main/res/layout/activity_reportcreation.xml
  9. 20
      app/src/main/res/layout/fragment_report_creation.xml
  10. 13
      app/src/main/res/layout/fragment_reports.xml
  11. 5
      app/src/main/res/values/strings.xml

@ -118,6 +118,11 @@
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="portrait" /> android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.ReportCreationActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<meta-data <meta-data
android:name="preloaded_fonts" android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" /> android:resource="@array/preloaded_fonts" />

@ -14,10 +14,12 @@ import net.pokeranalytics.android.model.Criteria.TournamentFeatures.comparison
import net.pokeranalytics.android.model.Criteria.TournamentFees.comparison import net.pokeranalytics.android.model.Criteria.TournamentFees.comparison
import net.pokeranalytics.android.model.Criteria.TournamentNames.comparison import net.pokeranalytics.android.model.Criteria.TournamentNames.comparison
import net.pokeranalytics.android.model.Criteria.TournamentTypes.comparison import net.pokeranalytics.android.model.Criteria.TournamentTypes.comparison
import net.pokeranalytics.android.model.Criteria.TransactionTypes.comparison
import net.pokeranalytics.android.model.filter.Query import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.NameManageable import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.realm.* import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.ui.view.RowRepresentable
fun List<Criteria>.combined(): List<Query> { fun List<Criteria>.combined(): List<Query> {
val comparatorList = ArrayList<List<Query>>() val comparatorList = ArrayList<List<Query>>()
@ -29,7 +31,9 @@ fun List<Criteria>.combined(): List<Query> {
fun getCombinations(queries: List<List<Query>>): List<Query> { fun getCombinations(queries: List<List<Query>>): List<Query> {
if (queries.size == 0) { return listOf() } if (queries.size == 0) {
return listOf()
}
val mutableQueries = queries.toMutableList() val mutableQueries = queries.toMutableList()
var combinations = mutableQueries.removeAt(0) var combinations = mutableQueries.removeAt(0)
@ -49,49 +53,14 @@ fun getCombinations(queries: List<List<Query>>): List<Query> {
return combinations return combinations
} }
//fun getCombinations(lists: List<Query>): List<Query> { sealed class Criteria : RowRepresentable {
// var combinations: LinkedHashSet<Query> = LinkedHashSet()
// var newCombinations: LinkedHashSet<Query>
//
// var index = 0
//
// // extract each of the integers in the first list
// // and add each to ints as a new list
// if (lists.isNotEmpty()) {
// for (i in lists[0]) {
// val newList = ArrayList<T>()
// newList.add(i)
// combinations.add(newList)
// }
// index++
// }
// while (index < lists.size) {
// val nextList = lists[index]
// newCombinations = LinkedHashSet()
// for (first in combinations) {
// for (second in nextList) {
// val newList = ArrayList<T>()
// newList.addAll(first)
// newList.add(second)
// newCombinations.add(newList)
// }
// }
// combinations = newCombinations
//
// index++
// }
//
// return combinations.toList()
//}
sealed class Criteria {
abstract class RealmCriteria : Criteria() { abstract class RealmCriteria : Criteria() {
inline fun <reified T: NameManageable> comparison(): List<Query> { inline fun <reified T : NameManageable> comparison(): List<Query> {
return compare<QueryCondition.QueryDataCondition<NameManageable>, T>() return compare<QueryCondition.QueryDataCondition<NameManageable>, T>()
} }
} }
abstract class SimpleCriteria(private val conditions:List<QueryCondition>): Criteria() { abstract class SimpleCriteria(private val conditions: List<QueryCondition>) : Criteria() {
fun comparison(): List<Query> { fun comparison(): List<Query> {
return conditions.map { Query(it) } return conditions.map { Query(it) }
} }
@ -102,11 +71,21 @@ sealed class Criteria {
QueryCondition.distinct<Session, T, S>()?.let { QueryCondition.distinct<Session, T, S>()?.let {
val values = it.mapNotNull { session -> val values = it.mapNotNull { session ->
when (this) { when (this) {
is Limits -> if (session.limit is S) { session.limit as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue is Limits -> if (session.limit is S) {
is TournamentTypes -> if (session.tournamentType is S) { session.tournamentType as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue session.limit as S
is TableSizes -> if (session.tableSize is S) { session.tableSize as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
is TournamentFees -> if (session.tournamentEntryFee is S) { session.tournamentEntryFee as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue is TournamentTypes -> if (session.tournamentType is S) {
is Blinds -> if (session.blinds is S) { session.blinds as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue session.tournamentType as S
} else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
is TableSizes -> if (session.tableSize is S) {
session.tableSize as S
} else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
is TournamentFees -> if (session.tournamentEntryFee is S) {
session.tournamentEntryFee as S
} else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
is Blinds -> if (session.blinds is S) {
session.blinds as S
} else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
else -> null else -> null
} }
}.distinct() }.distinct()
@ -117,33 +96,39 @@ sealed class Criteria {
} }
object Bankrolls: RealmCriteria() object Bankrolls : RealmCriteria()
object Games: RealmCriteria() object Games : RealmCriteria()
object TournamentNames: RealmCriteria() object TournamentNames : RealmCriteria()
object Locations: RealmCriteria() object Locations : RealmCriteria()
object TournamentFeatures: RealmCriteria() object TournamentFeatures : RealmCriteria()
object TransactionTypes: RealmCriteria() object TransactionTypes : RealmCriteria()
object Limits: ListCriteria() object Limits : ListCriteria()
object TableSizes: ListCriteria() object TableSizes : ListCriteria()
object TournamentTypes: ListCriteria() object TournamentTypes : ListCriteria()
object MonthsOfYear: SimpleCriteria(List(12) { index -> QueryCondition.AnyMonthOfYear().apply { listOfValues = arrayListOf(index)} }) object MonthsOfYear : SimpleCriteria(List(12) { index ->
object DaysOfWeek: SimpleCriteria(List(7) { index -> QueryCondition.AnyDayOfWeek().apply { listOfValues = arrayListOf(index + 1) } }) QueryCondition.AnyMonthOfYear().apply { listOfValues = arrayListOf(index) }
object SessionTypes: SimpleCriteria(listOf(QueryCondition.IsCash, QueryCondition.IsTournament)) })
object BankrollTypes: SimpleCriteria(listOf(QueryCondition.IsLive, QueryCondition.IsOnline))
object DayPeriods: SimpleCriteria(listOf(QueryCondition.IsWeekDay, QueryCondition.IsWeekEnd)) object DaysOfWeek : SimpleCriteria(List(7) { index ->
object Years: ListCriteria() QueryCondition.AnyDayOfWeek().apply { listOfValues = arrayListOf(index + 1) }
object AllMonthsUpToNow: ListCriteria() })
object Blinds: ListCriteria()
object TournamentFees: ListCriteria() object SessionTypes : SimpleCriteria(listOf(QueryCondition.IsCash, QueryCondition.IsTournament))
object Cash: SimpleCriteria(listOf(QueryCondition.IsCash)) object BankrollTypes : SimpleCriteria(listOf(QueryCondition.IsLive, QueryCondition.IsOnline))
object Tournament: SimpleCriteria(listOf(QueryCondition.IsTournament)) object DayPeriods : SimpleCriteria(listOf(QueryCondition.IsWeekDay, QueryCondition.IsWeekEnd))
object Years : ListCriteria()
object AllMonthsUpToNow : ListCriteria()
object Blinds : ListCriteria()
object TournamentFees : ListCriteria()
object Cash : SimpleCriteria(listOf(QueryCondition.IsCash))
object Tournament : SimpleCriteria(listOf(QueryCondition.IsTournament))
val queries: List<Query> val queries: List<Query>
get() { get() {
return when (this) { return when (this) {
is AllMonthsUpToNow -> { is AllMonthsUpToNow -> {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
val firstSession= realm.where<Session>().sort("startDate", Sort.ASCENDING).findFirst() val firstSession = realm.where<Session>().sort("startDate", Sort.ASCENDING).findFirst()
val lastSession = realm.where<Session>().sort("startDate", Sort.DESCENDING).findFirst() val lastSession = realm.where<Session>().sort("startDate", Sort.DESCENDING).findFirst()
realm.close() realm.close()
@ -186,12 +171,12 @@ sealed class Criteria {
is TournamentFeatures -> comparison<TournamentFeature>() is TournamentFeatures -> comparison<TournamentFeature>()
is TournamentNames -> comparison<TournamentName>() is TournamentNames -> comparison<TournamentName>()
is Locations -> comparison<Location>() is Locations -> comparison<Location>()
is TransactionTypes-> comparison<TransactionType>() is TransactionTypes -> comparison<TransactionType>()
is SimpleCriteria -> comparison() is SimpleCriteria -> comparison()
is Limits -> comparison<QueryCondition.AnyLimit, Int>() is Limits -> comparison<QueryCondition.AnyLimit, Int>()
is TournamentTypes -> comparison<QueryCondition.AnyTournamentType, Int>() is TournamentTypes -> comparison<QueryCondition.AnyTournamentType, Int>()
is TableSizes -> comparison<QueryCondition.AnyTableSize, Int>() is TableSizes -> comparison<QueryCondition.AnyTableSize, Int>()
is TournamentFees -> comparison<QueryCondition.TournamentFee, Double >() is TournamentFees -> comparison<QueryCondition.TournamentFee, Double>()
is Years -> { is Years -> {
val years = arrayListOf<Query>() val years = arrayListOf<Query>()
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
@ -209,13 +194,13 @@ sealed class Criteria {
realm.close() realm.close()
years years
} }
is Blinds -> comparison<QueryCondition.AnyBlind, String >() is Blinds -> comparison<QueryCondition.AnyBlind, String>()
else -> throw PokerAnalyticsException.QueryTypeUnhandled else -> throw PokerAnalyticsException.QueryTypeUnhandled
} }
} }
companion object { companion object {
inline fun <reified S : QueryCondition.QueryDataCondition<NameManageable >, reified T : NameManageable> compare(): List<Query> { inline fun <reified S : QueryCondition.QueryDataCondition<NameManageable>, reified T : NameManageable> compare(): List<Query> {
val objects = mutableListOf<S>() val objects = mutableListOf<S>()
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
realm.where<T>().findAll().forEach { realm.where<T>().findAll().forEach {
@ -229,10 +214,10 @@ sealed class Criteria {
return objects.map { Query(it) } return objects.map { Query(it) }
} }
inline fun < reified S : QueryCondition.ListOfValues<T>, T:Any > compareList(values:List<T>): List<Query> { inline fun <reified S : QueryCondition.ListOfValues<T>, T : Any> compareList(values: List<T>): List<Query> {
val objects = mutableListOf<S>() val objects = mutableListOf<S>()
values.forEach { values.forEach {
val condition =(S::class.java.newInstance()).apply { val condition = (S::class.java.newInstance()).apply {
listOfValues = arrayListOf(it) listOfValues = arrayListOf(it)
} }
objects.add(condition) objects.add(condition)
@ -240,5 +225,19 @@ sealed class Criteria {
objects.sorted() objects.sorted()
return objects.map { Query(it) } return objects.map { Query(it) }
} }
val all: List<Criteria>
get() {
return listOf(
Bankrolls, Games, TournamentNames, Locations,
TournamentFeatures, Limits, TableSizes, TournamentTypes,
MonthsOfYear, DaysOfWeek, SessionTypes,
BankrollTypes, DayPeriods, Years,
AllMonthsUpToNow, Blinds, TournamentFees
)
}
} }
} }

@ -3,20 +3,11 @@ package net.pokeranalytics.android.model.realm
import io.realm.* import io.realm.*
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.filter.Filterable import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.Query import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.CountableUsage
import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow
import net.pokeranalytics.android.ui.view.rowrepresentable.GameRow
import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow
import net.pokeranalytics.android.util.NULL_TEXT
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
@ -51,6 +42,11 @@ open class Filter : RealmObject(), RowRepresentable {
Timber.d(">>> Filter query: ${realmQuery.description}") Timber.d(">>> Filter query: ${realmQuery.description}")
return realmQuery.findAll() return realmQuery.findAll()
} }
fun sortedByUsage(realm: Realm): RealmResults<Filter> {
return realm.where(Filter::class.java).findAll().sort("usageCount")
}
} }
@PrimaryKey @PrimaryKey

@ -3,12 +3,13 @@ package net.pokeranalytics.android.model.realm
import io.realm.RealmList import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.ui.view.RowRepresentable
import java.util.* import java.util.*
enum class ReportDisplay { enum class ReportDisplay : RowRepresentable {
TABLE, FIGURES,
GRAPH, EVO_GRAPH,
MAP COMPARISON_GRAPH
} }
open class ReportSetup : RealmObject() { open class ReportSetup : RealmObject() {
@ -20,7 +21,7 @@ open class ReportSetup : RealmObject() {
var name: String = "" var name: String = ""
// The type of display of the report // The type of display of the report
var display: Int = ReportDisplay.TABLE.ordinal var display: Int = ReportDisplay.FIGURES.ordinal
// @todo define the configuration options // @todo define the configuration options

@ -0,0 +1,24 @@
package net.pokeranalytics.android.ui.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
class ReportCreationActivity : PokerAnalyticsActivity() {
companion object {
fun newInstance(context: Context) {
val intent = Intent(context, ReportCreationActivity::class.java)
context.startActivity(intent)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_reportcreation)
}
}

@ -0,0 +1,102 @@
package net.pokeranalytics.android.ui.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import io.realm.Realm
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.model.Criteria
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.model.realm.ReportDisplay
import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.view.RowRepresentable
class ReportCreationFragment : RealmFragment() {
class Process {
var step: Step = Step.TYPE
var display: ReportDisplay? = null
var stats = listOf<Stat>()
var comparators = listOf<Criteria>()
var useFilter: Boolean? = null
var filter: Filter? = null
enum class Step {
TYPE,
STAT,
COMPARATOR,
FILTER,
FINALIZE
}
val nextStep: Step
get() {
return when (this.display) {
null -> Step.TYPE
else -> {
if (this.stats.isEmpty()) {
Step.STAT
} else if (this.display!! == ReportDisplay.COMPARISON_GRAPH && this.comparators.isEmpty()) {
Step.COMPARATOR
} else if (this.useFilter == null) {
Step.FILTER
} else {
Step.FINALIZE
}
}
}
}
fun titleForStep(step: Step) : Int? {
return when (step) {
Step.TYPE -> R.string.new_report_step_type
Step.STAT -> R.string.new_report_step_stat
Step.COMPARATOR -> R.string.new_report_step_comparator
Step.FILTER -> R.string.new_report_step_filter
else -> null
}
}
val dataSource: List<RowRepresentable>
get() {
return when (this.step) {
Step.TYPE -> return listOf(ReportDisplay.FIGURES, ReportDisplay.EVO_GRAPH, ReportDisplay.COMPARISON_GRAPH)
Step.STAT -> return Stat.values().toList()
Step.COMPARATOR -> Criteria.all
Step.FILTER -> {
val realm = Realm.getDefaultInstance()
val filters = Filter.sortedByUsage(realm)
realm.close()
filters
}
else -> listOf()
}
}
val nextButtonShouldAppear: Boolean
get() {
return when (this.step) {
Step.STAT, Step.COMPARATOR, Step.FILTER -> true
else -> false
}
}
val nextButtonTitle: Int
get() {
return when (this.step) {
Step.FILTER -> R.string.launch_report
else -> R.string.next
}
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
return inflater.inflate(R.layout.fragment_report_creation, container, false)
}
}

@ -7,7 +7,8 @@ import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import io.realm.Realm import io.realm.Realm
import kotlinx.android.synthetic.main.fragment_stats.* import kotlinx.android.synthetic.main.fragment_data_list.*
import kotlinx.android.synthetic.main.fragment_stats.recyclerView
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -16,6 +17,7 @@ import net.pokeranalytics.android.calculus.Calculator
import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.model.Criteria import net.pokeranalytics.android.model.Criteria
import net.pokeranalytics.android.model.combined import net.pokeranalytics.android.model.combined
import net.pokeranalytics.android.ui.activity.ReportCreationActivity
import net.pokeranalytics.android.ui.activity.ReportDetailsActivity import net.pokeranalytics.android.ui.activity.ReportDetailsActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
@ -100,6 +102,11 @@ class ReportsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSour
layoutManager = viewManager layoutManager = viewManager
adapter = reportsAdapter adapter = reportsAdapter
} }
this.addButton.setOnClickListener {
ReportCreationActivity.newInstance(requireContext())
}
} }
/** /**

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<fragment
android:id="@+id/reportCreationFragment"
android:name="net.pokeranalytics.android.ui.fragment.ReportCreationFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@layout/fragment_report_creation" />
</LinearLayout>

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
app:title="@string/new_report"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>

@ -15,4 +15,17 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/addButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:src="@drawable/ic_add"
android:tint="@color/black"
app:fabSize="normal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

@ -24,6 +24,11 @@
<string name="f_support">Support</string> <string name="f_support">Support</string>
<string name="f_support_desc">We try to answer as quickly as we can, in english or french !</string> <string name="f_support_desc">We try to answer as quickly as we can, in english or french !</string>
<string name="loading_please_wait">Loading, please wait&#8230;</string> <string name="loading_please_wait">Loading, please wait&#8230;</string>
<string name="new_report_step_type">Select your type of report</string>
<string name="new_report_step_stat">Select one or more statistics</string>
<string name="new_report_step_comparator">Select one or more comparison criteria</string>
<string name="new_report_step_filter">Select a filter if you want, or launch report</string>
<string name="launch_report">Launch Report</string>
<string name="address">Address</string> <string name="address">Address</string>
<string name="suggestions">Naming suggestions</string> <string name="suggestions">Naming suggestions</string>

Loading…
Cancel
Save