Compare commits
No commits in common. 'master' and 'od' have entirely different histories.
@ -1,139 +0,0 @@ |
|||||||
# CLAUDE.md |
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
|
||||||
|
|
||||||
## Project Overview |
|
||||||
|
|
||||||
This is **Poker Analytics**, a comprehensive Android application for tracking and analyzing poker sessions. The app supports both cash games and tournaments, providing detailed statistics, reporting, and data visualization capabilities. |
|
||||||
|
|
||||||
### Key Features |
|
||||||
- Session tracking (cash games and tournaments) |
|
||||||
- Advanced filtering and reporting |
|
||||||
- Bankroll management |
|
||||||
- Hand history import and replay |
|
||||||
- Statistical analysis and charting |
|
||||||
- Data backup and export functionality |
|
||||||
- Multi-currency support |
|
||||||
|
|
||||||
## Development Commands |
|
||||||
|
|
||||||
### Building the Project |
|
||||||
```bash |
|
||||||
./gradlew assembleStandardRelease # Build release APK |
|
||||||
./gradlew assembleStandardDebug # Build debug APK |
|
||||||
./gradlew build # Build all variants |
|
||||||
``` |
|
||||||
|
|
||||||
### Running Tests |
|
||||||
```bash |
|
||||||
./gradlew test # Run unit tests |
|
||||||
./gradlew connectedAndroidTest # Run instrumented tests (requires device/emulator) |
|
||||||
./gradlew testStandardDebugUnitTest # Run specific unit tests |
|
||||||
``` |
|
||||||
|
|
||||||
### Cleaning |
|
||||||
```bash |
|
||||||
./gradlew clean # Clean build artifacts |
|
||||||
``` |
|
||||||
|
|
||||||
### Build Configuration |
|
||||||
- **Target SDK**: 35 (Android 15) |
|
||||||
- **Min SDK**: 23 (Android 6.0) |
|
||||||
- **Build Tools**: 30.0.3 |
|
||||||
- **Kotlin Version**: 1.9.24 |
|
||||||
- **Realm Schema Version**: 14 |
|
||||||
|
|
||||||
## Architecture Overview |
|
||||||
|
|
||||||
### Package Structure |
|
||||||
The main source code is organized under `app/src/main/java/net/pokeranalytics/android/`: |
|
||||||
|
|
||||||
#### Core Components |
|
||||||
- **`model/`** - Data models and business logic |
|
||||||
- `realm/` - Realm database models (Session, Bankroll, Result, etc.) |
|
||||||
- `filter/` - Query system for filtering sessions |
|
||||||
- `migrations/` - Database migration handling |
|
||||||
- `handhistory/` - Hand history data structures |
|
||||||
|
|
||||||
- **`ui/`** - User interface components |
|
||||||
- `activity/` - Main activities (HomeActivity, SessionActivity, etc.) |
|
||||||
- `fragment/` - UI fragments organized by feature |
|
||||||
- `adapter/` - RecyclerView adapters and data sources |
|
||||||
- `modules/` - Feature-specific UI modules |
|
||||||
|
|
||||||
- **`calculus/`** - Statistics and calculation engine |
|
||||||
- Core calculation logic for poker statistics |
|
||||||
- Report generation system |
|
||||||
- Performance tracking |
|
||||||
|
|
||||||
- **`util/`** - Utility classes |
|
||||||
- `csv/` - CSV import/export functionality |
|
||||||
- `billing/` - In-app purchase handling |
|
||||||
- `extensions/` - Kotlin extension functions |
|
||||||
|
|
||||||
#### Key Classes |
|
||||||
- **`Session`** (`model/realm/Session.kt`): Core session data model |
|
||||||
- **`HomeActivity`** (`ui/activity/HomeActivity.kt`): Main app entry point with tab navigation |
|
||||||
- **`PokerAnalyticsApplication`**: Application class handling initialization |
|
||||||
|
|
||||||
### Database Architecture |
|
||||||
The app uses **Realm** database with these key entities: |
|
||||||
- `Session` - Individual poker sessions |
|
||||||
- `Bankroll` - Bankroll management |
|
||||||
- `Result` - Session results and statistics |
|
||||||
- `ComputableResult` - Pre-computed statistics for performance |
|
||||||
- `Filter` - Saved filter configurations |
|
||||||
- `HandHistory` - Hand-by-hand game data |
|
||||||
|
|
||||||
### UI Architecture |
|
||||||
- **MVVM pattern** with Android Architecture Components |
|
||||||
- **Fragment-based navigation** with bottom navigation tabs |
|
||||||
- **Custom RecyclerView adapters** for data presentation |
|
||||||
- **Material Design** components |
|
||||||
|
|
||||||
## Key Technologies |
|
||||||
|
|
||||||
### Core Dependencies |
|
||||||
- **Realm Database** (10.15.1) - Local data storage |
|
||||||
- **Kotlin Coroutines** - Asynchronous programming |
|
||||||
- **Firebase Crashlytics** - Crash reporting and analytics |
|
||||||
- **Material Design Components** - UI framework |
|
||||||
- **MPAndroidChart** - Data visualization |
|
||||||
- **CameraX** - Image capture functionality |
|
||||||
|
|
||||||
### Testing |
|
||||||
- **JUnit** for unit testing |
|
||||||
- **Android Instrumented Tests** for integration testing |
|
||||||
- Test files located in `app/src/androidTest/` and `app/src/test/` |
|
||||||
|
|
||||||
## Development Guidelines |
|
||||||
|
|
||||||
### Working with Sessions |
|
||||||
- Sessions are the core data model representing individual poker games |
|
||||||
- Use `Session.newInstance()` to create new sessions properly |
|
||||||
- Always call `computeStats()` after modifying session data |
|
||||||
- Sessions can be in various states: PENDING, STARTED, PAUSED, ENDED |
|
||||||
|
|
||||||
### Database Operations |
|
||||||
- Use Realm transactions for data modifications |
|
||||||
- The app uses schema version 14 - increment when making schema changes |
|
||||||
- Migration logic is in `PokerAnalyticsMigration.kt` |
|
||||||
|
|
||||||
### Testing Data |
|
||||||
- Use `FakeDataManager.createFakeSessions()` for generating test data |
|
||||||
- Seed data is available through the `Seed` class |
|
||||||
|
|
||||||
### Build Variants |
|
||||||
- **standard** - Main production flavor |
|
||||||
- Release builds are optimized and obfuscated with ProGuard |
|
||||||
|
|
||||||
## Performance Considerations |
|
||||||
- Sessions use `ComputableResult` for pre-computed statistics |
|
||||||
- Large datasets are handled with Realm's lazy loading |
|
||||||
- Chart rendering is optimized for large data sets |
|
||||||
- Background processing uses Kotlin Coroutines |
|
||||||
|
|
||||||
## Security & Privacy |
|
||||||
- Sensitive data is encrypted in Realm database |
|
||||||
- Crash logging excludes personal information |
|
||||||
- Backup functionality includes data encryption |
|
||||||
|
Before Width: | Height: | Size: 161 KiB |
@ -1,75 +0,0 @@ |
|||||||
package net.pokeranalytics.android.api |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import kotlinx.coroutines.CoroutineScope |
|
||||||
import kotlinx.coroutines.Dispatchers |
|
||||||
import kotlinx.coroutines.async |
|
||||||
import net.pokeranalytics.android.util.CrashLogging |
|
||||||
import net.pokeranalytics.android.util.extensions.isNetworkAvailable |
|
||||||
import okhttp3.MediaType |
|
||||||
import okhttp3.MultipartBody |
|
||||||
import okhttp3.RequestBody |
|
||||||
import retrofit2.Call |
|
||||||
import retrofit2.Retrofit |
|
||||||
import retrofit2.http.Multipart |
|
||||||
import retrofit2.http.POST |
|
||||||
import retrofit2.http.Part |
|
||||||
import timber.log.Timber |
|
||||||
|
|
||||||
object RetrofitClient { |
|
||||||
private const val BASE_URL = "https://www.pokeranalytics.net/backup/" |
|
||||||
fun getClient(): Retrofit = |
|
||||||
Retrofit.Builder() |
|
||||||
.baseUrl(BASE_URL) |
|
||||||
.build() |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
class BackupService { |
|
||||||
private val retrofit = RetrofitClient.getClient() |
|
||||||
val backupApi: MyBackupApi = retrofit.create(MyBackupApi::class.java) |
|
||||||
} |
|
||||||
|
|
||||||
interface MyBackupApi { |
|
||||||
@Multipart |
|
||||||
@POST("send") |
|
||||||
fun postFile(@Part mail: MultipartBody.Part, @Part fileBody: MultipartBody.Part): Call<Void> |
|
||||||
} |
|
||||||
|
|
||||||
object BackupApi { |
|
||||||
|
|
||||||
private val service = BackupService() |
|
||||||
|
|
||||||
// curl -F recipient=laurent@staxriver.com -F file=@test.txt https://www.pokeranalytics.net/backup/send |
|
||||||
suspend fun backupFile(context: Context, mail: String, fileName: String, fileContent: String): Boolean { |
|
||||||
|
|
||||||
val filePart = MultipartBody.Part.createFormData( |
|
||||||
"file", |
|
||||||
fileName, |
|
||||||
RequestBody.create(MediaType.parse("text/csv"), fileContent) |
|
||||||
) |
|
||||||
|
|
||||||
val mailPart = MultipartBody.Part.createFormData("recipient", mail) |
|
||||||
|
|
||||||
return if (context.isNetworkAvailable()) { |
|
||||||
var success = false |
|
||||||
val job = CoroutineScope(context = Dispatchers.IO).async { |
|
||||||
success = try { |
|
||||||
val response = service.backupApi.postFile(mailPart, filePart).execute() |
|
||||||
Timber.d("response code = ${response.code()}") |
|
||||||
Timber.d("success = ${response.isSuccessful}") |
|
||||||
true |
|
||||||
} catch (e: Exception) { |
|
||||||
Timber.d("!!! backup failed: ${e.message}") |
|
||||||
CrashLogging.logException(e) |
|
||||||
false |
|
||||||
} |
|
||||||
} |
|
||||||
job.await() |
|
||||||
return success |
|
||||||
} else { |
|
||||||
false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,49 +0,0 @@ |
|||||||
package net.pokeranalytics.android.api |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import com.android.volley.Request |
|
||||||
import com.android.volley.toolbox.JsonArrayRequest |
|
||||||
import com.android.volley.toolbox.Volley |
|
||||||
import org.json.JSONArray |
|
||||||
import timber.log.Timber |
|
||||||
|
|
||||||
data class BlogPost(var id: Int, var content: String) |
|
||||||
|
|
||||||
private fun JSONArray.toBlogPosts(): List<BlogPost> { |
|
||||||
|
|
||||||
val posts = mutableListOf<BlogPost>() |
|
||||||
(0 until this.length()).forEach { index -> |
|
||||||
val jo = this.getJSONObject(index) |
|
||||||
val post = BlogPost(jo.getInt("id"), jo.getJSONObject("content").getString("rendered")) |
|
||||||
posts.add(post) |
|
||||||
} |
|
||||||
return posts |
|
||||||
} |
|
||||||
|
|
||||||
class BlogPostApi { |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
private const val tipsLastPostsURL = "https://www.poker-analytics.net/blog/wp-json/wp/v2/posts/?categories=109\n" |
|
||||||
|
|
||||||
fun getLatestPosts(context: Context, callback: (List<BlogPost>) -> (Unit)) { |
|
||||||
|
|
||||||
val queue = Volley.newRequestQueue(context) |
|
||||||
|
|
||||||
val jsonObjectRequest = JsonArrayRequest( |
|
||||||
Request.Method.GET, tipsLastPostsURL, null, |
|
||||||
{ response -> |
|
||||||
// Timber.d("posts = $response") |
|
||||||
callback(response.toBlogPosts()) |
|
||||||
}, |
|
||||||
{ error -> |
|
||||||
Timber.w("Error while retrieving blog posts: $error") |
|
||||||
} |
|
||||||
) |
|
||||||
|
|
||||||
queue.add(jsonObjectRequest) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,60 +1,80 @@ |
|||||||
package net.pokeranalytics.android.api |
package net.pokeranalytics.android.api |
||||||
|
|
||||||
import android.content.Context |
import android.content.Context |
||||||
import androidx.annotation.Keep |
import net.pokeranalytics.android.BuildConfig |
||||||
import com.android.volley.VolleyError |
import net.pokeranalytics.android.R |
||||||
import com.android.volley.toolbox.StringRequest |
import net.pokeranalytics.android.model.retrofit.CurrencyConverterValue |
||||||
import com.android.volley.toolbox.Volley |
import net.pokeranalytics.android.util.URL |
||||||
import kotlinx.serialization.Serializable |
import okhttp3.Interceptor |
||||||
import kotlinx.serialization.decodeFromString |
import okhttp3.OkHttpClient |
||||||
import kotlinx.serialization.json.Json |
import okhttp3.logging.HttpLoggingInterceptor |
||||||
import timber.log.Timber |
import retrofit2.Call |
||||||
|
import retrofit2.Retrofit |
||||||
|
import retrofit2.converter.gson.GsonConverterFactory |
||||||
|
import retrofit2.http.GET |
||||||
|
import retrofit2.http.Query |
||||||
|
import java.util.concurrent.TimeUnit |
||||||
|
|
||||||
|
/** |
||||||
|
* CurrencyCode Converter API |
||||||
|
*/ |
||||||
|
interface CurrencyConverterApi { |
||||||
|
|
||||||
@Keep |
companion object { |
||||||
@Serializable |
|
||||||
data class RateResponse(var info: RateInfo) |
|
||||||
|
|
||||||
@Keep |
private var currencyConverterApi: CurrencyConverterApi? = null |
||||||
@Serializable |
|
||||||
data class RateInfo(var rate: Double) |
|
||||||
|
|
||||||
class CurrencyConverterApi { |
fun getApi(context: Context): CurrencyConverterApi? { |
||||||
|
|
||||||
companion object { |
if (currencyConverterApi == null) { |
||||||
|
|
||||||
val json = Json { ignoreUnknownKeys = true } |
var serviceEndpoint = URL.API_CURRENCY_CONVERTER |
||||||
|
|
||||||
fun currencyRate(fromCurrency: String, toCurrency: String, context: Context, callback: (Double?, VolleyError?) -> (Unit)) { |
val httpClient = OkHttpClient.Builder() |
||||||
|
|
||||||
val queue = Volley.newRequestQueue(context) |
// Logging interceptor |
||||||
val url = "https://api.apilayer.com/exchangerates_data/convert?to=$toCurrency&from=$fromCurrency&amount=1" |
if (BuildConfig.DEBUG) { |
||||||
|
val interceptor = HttpLoggingInterceptor() |
||||||
|
interceptor.level = HttpLoggingInterceptor.Level.BASIC |
||||||
|
httpClient.addInterceptor(interceptor) |
||||||
|
} |
||||||
|
|
||||||
Timber.d("Api call = $url") |
// Add headers |
||||||
|
val interceptor = Interceptor { chain -> |
||||||
|
val original = chain.request() |
||||||
|
val originalHttpUrl = original.url() |
||||||
|
|
||||||
val stringRequest = object : StringRequest( |
val url = originalHttpUrl.newBuilder() |
||||||
Method.GET, url, |
.addQueryParameter("apiKey", context.getString(R.string.currency_converter_api)) |
||||||
{ response -> |
.build() |
||||||
|
|
||||||
val o = json.decodeFromString<RateResponse>(response) |
val requestBuilder = original.newBuilder() |
||||||
Timber.d("rate = ${o.info.rate}") |
.url(url) |
||||||
callback(o.info.rate, null) |
|
||||||
}, |
|
||||||
{ |
|
||||||
Timber.d("Api call failed: ${it.message}") |
|
||||||
callback(null, it) |
|
||||||
}) { |
|
||||||
|
|
||||||
override fun getHeaders(): MutableMap<String, String> { |
chain.proceed(requestBuilder.build()) |
||||||
val headers = HashMap<String, String>() |
|
||||||
headers["apikey"] = "XnfeyID3PMKd3k4zTPW0XmZAbcZlZgqH" |
|
||||||
return headers |
|
||||||
} |
} |
||||||
|
httpClient.addInterceptor(interceptor) |
||||||
|
|
||||||
|
val client = httpClient |
||||||
|
.readTimeout(60, TimeUnit.SECONDS) |
||||||
|
.connectTimeout(60, TimeUnit.SECONDS) |
||||||
|
.build() |
||||||
|
|
||||||
|
val retrofit = Retrofit.Builder() |
||||||
|
.addConverterFactory(GsonConverterFactory.create()) |
||||||
|
.baseUrl(serviceEndpoint.value) |
||||||
|
.client(client) |
||||||
|
.build() |
||||||
|
|
||||||
|
currencyConverterApi = retrofit.create(CurrencyConverterApi::class.java) |
||||||
} |
} |
||||||
queue.add(stringRequest) |
|
||||||
|
|
||||||
|
return currencyConverterApi |
||||||
} |
} |
||||||
|
|
||||||
} |
} |
||||||
|
|
||||||
} |
@GET("convert") |
||||||
|
fun convert(@Query("q") currencies: String, @Query("compact") compact: String = "y"): Call<Map<String, CurrencyConverterValue>> |
||||||
|
|
||||||
|
} |
||||||
|
|||||||
@ -1,242 +0,0 @@ |
|||||||
package net.pokeranalytics.android.api |
|
||||||
|
|
||||||
import com.android.volley.* |
|
||||||
import com.android.volley.toolbox.HttpHeaderParser |
|
||||||
import java.io.* |
|
||||||
|
|
||||||
|
|
||||||
open class VolleyMultipartRequest : Request<NetworkResponse?> { |
|
||||||
|
|
||||||
private val twoHyphens = "--" |
|
||||||
private val lineEnd = "\r\n" |
|
||||||
private val boundary = "apiclient-" + System.currentTimeMillis() |
|
||||||
private var mListener: Response.Listener<NetworkResponse> |
|
||||||
private var mErrorListener: Response.ErrorListener |
|
||||||
private var mHeaders: Map<String, String>? = null |
|
||||||
private var byteData: Map<String, DataPart>? = null |
|
||||||
|
|
||||||
/** |
|
||||||
* Default constructor with predefined header and post method. |
|
||||||
* |
|
||||||
* @param url request destination |
|
||||||
* @param headers predefined custom header |
|
||||||
* @param listener on success achieved 200 code from request |
|
||||||
* @param errorListener on error http or library timeout |
|
||||||
*/ |
|
||||||
constructor( |
|
||||||
url: String?, headers: Map<String, String>?, |
|
||||||
byteData: Map<String, DataPart>, |
|
||||||
listener: Response.Listener<NetworkResponse>, |
|
||||||
errorListener: Response.ErrorListener |
|
||||||
) : super(Method.POST, url, errorListener) { |
|
||||||
mListener = listener |
|
||||||
this.mErrorListener = errorListener |
|
||||||
mHeaders = headers |
|
||||||
this.byteData = byteData |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Constructor with option method and default header configuration. |
|
||||||
* |
|
||||||
* @param method method for now accept POST and GET only |
|
||||||
* @param url request destination |
|
||||||
* @param listener on success event handler |
|
||||||
* @param errorListener on error event handler |
|
||||||
*/ |
|
||||||
constructor( |
|
||||||
method: Int, url: String?, |
|
||||||
listener: Response.Listener<NetworkResponse>, |
|
||||||
errorListener: Response.ErrorListener |
|
||||||
) : super(method, url, errorListener) { |
|
||||||
mListener = listener |
|
||||||
this.mErrorListener = errorListener |
|
||||||
} |
|
||||||
|
|
||||||
@Throws(AuthFailureError::class) |
|
||||||
override fun getHeaders(): Map<String, String> { |
|
||||||
return if (mHeaders != null) mHeaders!! else super.getHeaders() |
|
||||||
} |
|
||||||
|
|
||||||
override fun getBodyContentType(): String { |
|
||||||
return "multipart/form-data;boundary=$boundary" |
|
||||||
} |
|
||||||
|
|
||||||
@Throws(AuthFailureError::class) |
|
||||||
override fun getBody(): ByteArray? { |
|
||||||
val bos = ByteArrayOutputStream() |
|
||||||
val dos = DataOutputStream(bos) |
|
||||||
try { |
|
||||||
// populate text payload |
|
||||||
val params = params |
|
||||||
if (params != null && params.isNotEmpty()) { |
|
||||||
textParse(dos, params, paramsEncoding) |
|
||||||
} |
|
||||||
|
|
||||||
// populate data byte payload |
|
||||||
val data = |
|
||||||
byteData |
|
||||||
if (data != null && data.isNotEmpty()) { |
|
||||||
dataParse(dos, data) |
|
||||||
} |
|
||||||
|
|
||||||
// close multipart form data after text and file data |
|
||||||
dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd) |
|
||||||
return bos.toByteArray() |
|
||||||
} catch (e: IOException) { |
|
||||||
e.printStackTrace() |
|
||||||
} |
|
||||||
return null |
|
||||||
} |
|
||||||
|
|
||||||
override fun parseNetworkResponse(response: NetworkResponse?): Response<NetworkResponse?> { |
|
||||||
return try { |
|
||||||
Response.success( |
|
||||||
response, |
|
||||||
HttpHeaderParser.parseCacheHeaders(response) |
|
||||||
) |
|
||||||
} catch (e: Exception) { |
|
||||||
Response.error(ParseError(e)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override fun deliverResponse(response: NetworkResponse?) { |
|
||||||
mListener.onResponse(response) |
|
||||||
} |
|
||||||
|
|
||||||
override fun deliverError(error: VolleyError?) { |
|
||||||
mErrorListener.onErrorResponse(error) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Parse string map into data output stream by key and value. |
|
||||||
* |
|
||||||
* @param dataOutputStream data output stream handle string parsing |
|
||||||
* @param params string inputs collection |
|
||||||
* @param encoding encode the inputs, default UTF-8 |
|
||||||
* @throws IOException |
|
||||||
*/ |
|
||||||
@Throws(IOException::class) |
|
||||||
private fun textParse( |
|
||||||
dataOutputStream: DataOutputStream, |
|
||||||
params: Map<String, String>, |
|
||||||
encoding: String |
|
||||||
) { |
|
||||||
try { |
|
||||||
for ((key, value) in params) { |
|
||||||
buildTextPart(dataOutputStream, key, value) |
|
||||||
} |
|
||||||
} catch (uee: UnsupportedEncodingException) { |
|
||||||
throw RuntimeException("Encoding not supported: $encoding", uee) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Parse data into data output stream. |
|
||||||
* |
|
||||||
* @param dataOutputStream data output stream handle file attachment |
|
||||||
* @param data loop through data |
|
||||||
* @throws IOException |
|
||||||
*/ |
|
||||||
@Throws(IOException::class) |
|
||||||
private fun dataParse(dataOutputStream: DataOutputStream, data: Map<String, DataPart>) { |
|
||||||
for ((key, value) in data) { |
|
||||||
buildDataPart(dataOutputStream, value, key) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Write string data into header and data output stream. |
|
||||||
* |
|
||||||
* @param dataOutputStream data output stream handle string parsing |
|
||||||
* @param parameterName name of input |
|
||||||
* @param parameterValue value of input |
|
||||||
* @throws IOException |
|
||||||
*/ |
|
||||||
@Throws(IOException::class) |
|
||||||
private fun buildTextPart( |
|
||||||
dataOutputStream: DataOutputStream, |
|
||||||
parameterName: String, |
|
||||||
parameterValue: String |
|
||||||
) { |
|
||||||
dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd) |
|
||||||
dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"$parameterName\"$lineEnd") |
|
||||||
//dataOutputStream.writeBytes("Content-Type: text/plain; charset=UTF-8" + lineEnd); |
|
||||||
dataOutputStream.writeBytes(lineEnd) |
|
||||||
dataOutputStream.writeBytes(parameterValue + lineEnd) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Write data file into header and data output stream. |
|
||||||
* |
|
||||||
* @param dataOutputStream data output stream handle data parsing |
|
||||||
* @param dataFile data byte as DataPart from collection |
|
||||||
* @param inputName name of data input |
|
||||||
* @throws IOException |
|
||||||
*/ |
|
||||||
@Throws(IOException::class) |
|
||||||
private fun buildDataPart( |
|
||||||
dataOutputStream: DataOutputStream, |
|
||||||
dataFile: DataPart, |
|
||||||
inputName: String |
|
||||||
) { |
|
||||||
dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd) |
|
||||||
dataOutputStream.writeBytes( |
|
||||||
"Content-Disposition: form-data; name=\"" + |
|
||||||
inputName + "\"; filename=\"" + dataFile.fileName + "\"" + lineEnd |
|
||||||
) |
|
||||||
if (dataFile.type != null && !dataFile.type!!.trim { it <= ' ' }.isEmpty()) { |
|
||||||
dataOutputStream.writeBytes("Content-Type: " + dataFile.type + lineEnd) |
|
||||||
} |
|
||||||
dataOutputStream.writeBytes(lineEnd) |
|
||||||
val fileInputStream = ByteArrayInputStream(dataFile.content) |
|
||||||
var bytesAvailable: Int = fileInputStream.available() |
|
||||||
val maxBufferSize = 1024 * 1024 |
|
||||||
var bufferSize = Math.min(bytesAvailable, maxBufferSize) |
|
||||||
val buffer = ByteArray(bufferSize) |
|
||||||
var bytesRead: Int = fileInputStream.read(buffer, 0, bufferSize) |
|
||||||
while (bytesRead > 0) { |
|
||||||
dataOutputStream.write(buffer, 0, bufferSize) |
|
||||||
bytesAvailable = fileInputStream.available() |
|
||||||
bufferSize = Math.min(bytesAvailable, maxBufferSize) |
|
||||||
bytesRead = fileInputStream.read(buffer, 0, bufferSize) |
|
||||||
} |
|
||||||
dataOutputStream.writeBytes(lineEnd) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Simple data container use for passing byte file |
|
||||||
*/ |
|
||||||
class DataPart { |
|
||||||
|
|
||||||
var fileName: String? = null |
|
||||||
|
|
||||||
var content: ByteArray? = null |
|
||||||
|
|
||||||
var type: String? = null |
|
||||||
|
|
||||||
/** |
|
||||||
* Constructor with data. |
|
||||||
* |
|
||||||
* @param name label of data |
|
||||||
* @param data byte data |
|
||||||
*/ |
|
||||||
constructor(name: String?, data: ByteArray) { |
|
||||||
fileName = name |
|
||||||
content = data |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Constructor with mime data type. |
|
||||||
* |
|
||||||
* @param name label of data |
|
||||||
* @param data byte data |
|
||||||
* @param mimeType mime data like "image/jpeg" |
|
||||||
*/ |
|
||||||
constructor(name: String?, data: ByteArray, mimeType: String?) { |
|
||||||
fileName = name |
|
||||||
content = data |
|
||||||
type = mimeType |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,111 +0,0 @@ |
|||||||
package net.pokeranalytics.android.calculus |
|
||||||
|
|
||||||
import io.realm.Realm |
|
||||||
import io.realm.RealmResults |
|
||||||
import net.pokeranalytics.android.model.filter.Query |
|
||||||
import net.pokeranalytics.android.model.filter.QueryCondition |
|
||||||
import net.pokeranalytics.android.model.realm.* |
|
||||||
import timber.log.Timber |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* A sessionGroup of computable items identified by a name |
|
||||||
*/ |
|
||||||
class ComputableGroup(val query: Query, var displayedStats: List<Stat>? = null) { |
|
||||||
|
|
||||||
/** |
|
||||||
* A subgroup used to compute stat variation |
|
||||||
*/ |
|
||||||
var comparedGroup: ComputableGroup? = null |
|
||||||
|
|
||||||
/** |
|
||||||
* The computed statIds of the comparable sessionGroup |
|
||||||
*/ |
|
||||||
var comparedComputedResults: ComputedResults? = null |
|
||||||
|
|
||||||
/** |
|
||||||
* A list of _conditions to get |
|
||||||
*/ |
|
||||||
val conditions: List<QueryCondition> |
|
||||||
get() { |
|
||||||
return this.query.conditions |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The size of the retrieved computables list |
|
||||||
*/ |
|
||||||
var size: Int = 0 |
|
||||||
|
|
||||||
/** |
|
||||||
* The list of endedSessions to compute |
|
||||||
*/ |
|
||||||
private var _computables: RealmResults<ComputableResult>? = null |
|
||||||
|
|
||||||
/** |
|
||||||
* Retrieves the computables on the relative [realm] filtered with the provided [conditions] |
|
||||||
*/ |
|
||||||
fun computables(realm: Realm, sorted: Boolean = false): RealmResults<ComputableResult> { |
|
||||||
|
|
||||||
// if computables exists and is valid (previous realm not closed) |
|
||||||
this._computables?.let { |
|
||||||
if (it.isValid) { |
|
||||||
return it |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val sortedField = if (sorted) "session.startDate" else null |
|
||||||
val computables = Filter.queryOn<ComputableResult>(realm, this.query, sortedField) |
|
||||||
|
|
||||||
this.size = computables.size |
|
||||||
this._computables = computables |
|
||||||
|
|
||||||
return computables |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The list of sets to compute |
|
||||||
*/ |
|
||||||
private var _sessionSets: RealmResults<SessionSet>? = null |
|
||||||
|
|
||||||
/** |
|
||||||
* Retrieves the session sets on the relative [realm] filtered with the provided [conditions] |
|
||||||
*/ |
|
||||||
fun sessionSets(realm: Realm, sorted: Boolean = false): RealmResults<SessionSet> { |
|
||||||
// if computables exists and is valid (previous realm not closed) |
|
||||||
this._sessionSets?.let { |
|
||||||
if (it.isValid) { |
|
||||||
return it |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val sortedField = if (sorted) SessionSet.Field.START_DATE.identifier else null |
|
||||||
val sets = Filter.queryOn<SessionSet>(realm, this.query, sortedField) |
|
||||||
this._sessionSets = sets |
|
||||||
return sets |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Retrieves the transactions on the relative [realm] filtered with the provided [conditions] |
|
||||||
*/ |
|
||||||
fun transactions(realm: Realm, transactionType: TransactionType, sorted: Boolean = false): RealmResults<Transaction> { |
|
||||||
val query = this.query.copy() |
|
||||||
query.add(QueryCondition.AnyTransactionType(transactionType)) |
|
||||||
val sortedField = if (sorted) "date" else null |
|
||||||
Timber.d("query = ${query.defaultName}") |
|
||||||
return Filter.queryOn(realm, query, sortedField) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Nullifies used Realm results |
|
||||||
*/ |
|
||||||
fun cleanup() { |
|
||||||
this._computables = null |
|
||||||
this._sessionSets = null |
|
||||||
} |
|
||||||
|
|
||||||
val isEmpty: Boolean |
|
||||||
get() { |
|
||||||
return this._computables?.isEmpty() ?: true |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,338 +0,0 @@ |
|||||||
package net.pokeranalytics.android.calculus |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import android.os.CountDownTimer |
|
||||||
import io.realm.Realm |
|
||||||
import io.realm.RealmQuery |
|
||||||
import io.realm.RealmResults |
|
||||||
import kotlinx.coroutines.CoroutineScope |
|
||||||
import kotlinx.coroutines.Dispatchers |
|
||||||
import kotlinx.coroutines.launch |
|
||||||
import net.pokeranalytics.android.calculus.optimalduration.CashGameOptimalDurationCalculator |
|
||||||
import net.pokeranalytics.android.model.LiveOnline |
|
||||||
import net.pokeranalytics.android.model.realm.* |
|
||||||
import net.pokeranalytics.android.ui.view.rows.StaticReport |
|
||||||
import net.pokeranalytics.android.util.CrashLogging |
|
||||||
import net.pokeranalytics.android.util.extensions.formattedHourlyDuration |
|
||||||
import timber.log.Timber |
|
||||||
import kotlin.coroutines.CoroutineContext |
|
||||||
|
|
||||||
|
|
||||||
interface NewPerformanceListener { |
|
||||||
fun newBestPerformanceHandler() |
|
||||||
} |
|
||||||
|
|
||||||
class ReportWhistleBlower(var context: Context) { |
|
||||||
|
|
||||||
private var sessions: RealmResults<Session>? = null |
|
||||||
private var results: RealmResults<Result>? = null |
|
||||||
|
|
||||||
private var currentTask: ReportTask? = null |
|
||||||
|
|
||||||
private val currentNotifications: MutableList<String> = mutableListOf() // Performance.id |
|
||||||
|
|
||||||
private val listeners: MutableList<NewPerformanceListener> = mutableListOf() |
|
||||||
|
|
||||||
var paused: Boolean = false |
|
||||||
|
|
||||||
private var timer: CountDownTimer? = null |
|
||||||
|
|
||||||
init { |
|
||||||
|
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
|
|
||||||
this.sessions = realm.where(Session::class.java).findAll() |
|
||||||
this.sessions?.addChangeListener { _ -> |
|
||||||
requestReportLaunch() |
|
||||||
} |
|
||||||
|
|
||||||
this.results = realm.where(Result::class.java).findAll() |
|
||||||
this.results?.addChangeListener { _ -> |
|
||||||
requestReportLaunch() |
|
||||||
} |
|
||||||
|
|
||||||
realm.close() |
|
||||||
} |
|
||||||
|
|
||||||
fun addListener(newPerformanceListener: NewPerformanceListener) { |
|
||||||
this.listeners.add(newPerformanceListener) |
|
||||||
} |
|
||||||
|
|
||||||
fun removeListener(listener: NewPerformanceListener) { |
|
||||||
this.listeners.remove(listener) |
|
||||||
} |
|
||||||
|
|
||||||
fun requestReportLaunch() { |
|
||||||
// Timber.d(">>> Launch report") |
|
||||||
|
|
||||||
if (paused) { |
|
||||||
CrashLogging.log("can't start reports comparisons because of paused state") |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
this.timer?.cancel() |
|
||||||
|
|
||||||
val launchStart = 100L |
|
||||||
val timer = object: CountDownTimer(launchStart, launchStart) { |
|
||||||
override fun onTick(p0: Long) { } |
|
||||||
|
|
||||||
override fun onFinish() { |
|
||||||
launchReportTask() |
|
||||||
} |
|
||||||
} |
|
||||||
this.timer = timer |
|
||||||
timer.start() |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
private fun launchReportTask() { |
|
||||||
|
|
||||||
synchronized(this) { |
|
||||||
this.currentTask?.cancel() |
|
||||||
|
|
||||||
val reportTask = ReportTask(this, this.context) |
|
||||||
this.currentTask = reportTask |
|
||||||
reportTask.start() |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Pauses the whistleblower, for example when importing data |
|
||||||
*/ |
|
||||||
fun pause() { |
|
||||||
this.paused = true |
|
||||||
this.currentTask?.cancel() |
|
||||||
this.currentTask = null |
|
||||||
} |
|
||||||
|
|
||||||
fun resume() { |
|
||||||
this.paused = false |
|
||||||
this.requestReportLaunch() |
|
||||||
} |
|
||||||
|
|
||||||
fun has(performanceId: String): Boolean { |
|
||||||
return this.currentNotifications.contains(performanceId) |
|
||||||
} |
|
||||||
|
|
||||||
fun notify(performance: Performance) { |
|
||||||
|
|
||||||
this.currentNotifications.add(performance.id) |
|
||||||
for (listener in this.listeners) { |
|
||||||
listener.newBestPerformanceHandler() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fun clearNotifications() { |
|
||||||
this.currentNotifications.clear() |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Context) { |
|
||||||
|
|
||||||
private var cancelled = false |
|
||||||
|
|
||||||
var handler: (() -> Unit)? = null |
|
||||||
|
|
||||||
private val coroutineContext: CoroutineContext |
|
||||||
get() = Dispatchers.Default |
|
||||||
|
|
||||||
fun start() { |
|
||||||
messages.add("Starting task...") |
|
||||||
launchReports() |
|
||||||
} |
|
||||||
|
|
||||||
fun cancel() { |
|
||||||
this.cancelled = true |
|
||||||
} |
|
||||||
|
|
||||||
var messages: MutableList<String> = mutableListOf() |
|
||||||
|
|
||||||
private fun launchReports() { |
|
||||||
CoroutineScope(coroutineContext).launch { |
|
||||||
|
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
|
|
||||||
// Basic |
|
||||||
for (basicReport in StaticReport.basicReports) { |
|
||||||
if (cancelled) { |
|
||||||
break |
|
||||||
} |
|
||||||
launchReport(realm, basicReport) |
|
||||||
} |
|
||||||
|
|
||||||
// CustomField |
|
||||||
val customFields = realm.where(CustomField::class.java) |
|
||||||
.equalTo("type", CustomField.Type.LIST.uniqueIdentifier).findAll() |
|
||||||
for (customField in customFields) { |
|
||||||
if (cancelled) { |
|
||||||
break |
|
||||||
} |
|
||||||
launchReport(realm, StaticReport.CustomFieldList(customField)) |
|
||||||
} |
|
||||||
realm.close() |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
private fun launchReport(realm: Realm, report: StaticReport) { |
|
||||||
|
|
||||||
// Timber.d(">>> launch report = $report") |
|
||||||
|
|
||||||
when (report) { |
|
||||||
StaticReport.OptimalDuration -> launchOptimalDuration(realm, report) |
|
||||||
else -> launchDefaultReport(realm, report) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private fun launchDefaultReport(realm: Realm, report: StaticReport) { |
|
||||||
val options = Calculator.Options( |
|
||||||
stats = report.stats, |
|
||||||
criterias = report.criteria |
|
||||||
) |
|
||||||
|
|
||||||
val result = Calculator.computeStats(realm, options = options) |
|
||||||
analyseDefaultReport(realm, report, result) |
|
||||||
} |
|
||||||
|
|
||||||
private fun launchOptimalDuration(realm: Realm, report: StaticReport) { |
|
||||||
LiveOnline.entries.forEach { key -> |
|
||||||
val duration = CashGameOptimalDurationCalculator.start(key.isLive) |
|
||||||
analyseOptimalDuration(realm, report, key, duration) |
|
||||||
} |
|
||||||
|
|
||||||
this.handler?.let { it() } |
|
||||||
} |
|
||||||
|
|
||||||
private fun analyseDefaultReport(realm: Realm, staticReport: StaticReport, result: Report) { |
|
||||||
|
|
||||||
messages.add("Analyse report $staticReport...") |
|
||||||
|
|
||||||
val nameSeparator = " " |
|
||||||
|
|
||||||
for (stat in result.options.stats) { |
|
||||||
|
|
||||||
// Timber.d("analyse stat: $stat for report: $staticReport") |
|
||||||
|
|
||||||
// Get current performance |
|
||||||
var query = performancesQuery(realm, staticReport, stat) |
|
||||||
|
|
||||||
val customField: CustomField? = |
|
||||||
(staticReport as? StaticReport.CustomFieldList)?.customField |
|
||||||
customField?.let { |
|
||||||
query = query.equalTo("customFieldId", it.id) |
|
||||||
} |
|
||||||
val currentPerf = query.findFirst() |
|
||||||
|
|
||||||
// Store if necessary, delete if necessary |
|
||||||
val bestComputedResults = result.max(stat) |
|
||||||
bestComputedResults?.let { computedResults -> |
|
||||||
messages.add("found new perf...") |
|
||||||
|
|
||||||
val performanceQuery = computedResults.group.query |
|
||||||
val performanceName = performanceQuery.getName(this.context, nameSeparator) |
|
||||||
|
|
||||||
Timber.d("Best computed = $performanceName, ${computedResults.computedStat(Stat.NET_RESULT)?.value}") |
|
||||||
|
|
||||||
var storePerf = true |
|
||||||
currentPerf?.let { |
|
||||||
messages.add("has current perf...") |
|
||||||
|
|
||||||
currentPerf.name?.let { name -> |
|
||||||
if (computedResults.group.query.getName(this.context, nameSeparator) == name) { |
|
||||||
storePerf = false |
|
||||||
} |
|
||||||
} |
|
||||||
currentPerf.objectId?.let { objectId -> |
|
||||||
if (computedResults.group.query.objectId == objectId) { |
|
||||||
storePerf = false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (storePerf) { |
|
||||||
realm.executeTransaction { |
|
||||||
currentPerf.name = performanceName |
|
||||||
currentPerf.objectId = performanceQuery.objectId |
|
||||||
currentPerf.customFieldId = customField?.id |
|
||||||
} |
|
||||||
this.whistleBlower.notify(currentPerf) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
messages.add("storePerf = $storePerf...") |
|
||||||
|
|
||||||
if (currentPerf == null && storePerf) { |
|
||||||
val performance = Performance( |
|
||||||
staticReport, |
|
||||||
stat, |
|
||||||
performanceName, |
|
||||||
performanceQuery.objectId, |
|
||||||
customField?.id, |
|
||||||
null |
|
||||||
) |
|
||||||
realm.executeTransaction { it.copyToRealm(performance) } |
|
||||||
this.whistleBlower.notify(performance) |
|
||||||
} |
|
||||||
|
|
||||||
} ?: run { // if there is no max but a now irrelevant Performance, we delete it |
|
||||||
|
|
||||||
messages.add("deletes current perf if necessary: $currentPerf...") |
|
||||||
|
|
||||||
// Timber.d("NO best computed value, current perf = $currentPerf ") |
|
||||||
currentPerf?.let { perf -> |
|
||||||
realm.executeTransaction { |
|
||||||
// Timber.d("Delete perf: stat = ${perf.stat}, report = ${perf.reportId}") |
|
||||||
perf.deleteFromRealm() |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
private fun analyseOptimalDuration(realm: Realm, staticReport: StaticReport, key: PerformanceKey, duration: Double?) { |
|
||||||
|
|
||||||
val performance = performancesQuery(realm, staticReport, key).findFirst() |
|
||||||
|
|
||||||
duration?.let { |
|
||||||
var storePerf = true |
|
||||||
|
|
||||||
val formattedDuration = (duration / 3600 / 1000).formattedHourlyDuration() |
|
||||||
performance?.let { perf -> |
|
||||||
if (perf.value == duration) { |
|
||||||
storePerf = false |
|
||||||
} |
|
||||||
|
|
||||||
if (storePerf) { |
|
||||||
realm.executeTransaction { |
|
||||||
perf.name = formattedDuration |
|
||||||
perf.value = duration |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (storePerf) { |
|
||||||
val perf = Performance(staticReport, key, name = formattedDuration, value = duration) |
|
||||||
realm.executeTransaction { it.copyToRealm(perf) } |
|
||||||
this.whistleBlower.notify(perf) |
|
||||||
} |
|
||||||
|
|
||||||
} ?: run { // no duration |
|
||||||
performance?.let { perf -> |
|
||||||
realm.executeTransaction { |
|
||||||
perf.deleteFromRealm() // delete if the perf exists |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
private fun performancesQuery(realm: Realm, staticReport: StaticReport, key: PerformanceKey): RealmQuery<Performance> { |
|
||||||
return realm.where(Performance::class.java) |
|
||||||
.equalTo("reportId", staticReport.uniqueIdentifier) |
|
||||||
.equalTo("key", key.value) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,12 +0,0 @@ |
|||||||
package net.pokeranalytics.android.calculus.calcul |
|
||||||
|
|
||||||
import net.pokeranalytics.android.calculus.AggregationType |
|
||||||
import net.pokeranalytics.android.ui.graph.Graph |
|
||||||
|
|
||||||
val AggregationType.axisFormatting: Graph.AxisFormatting |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
AggregationType.DURATION -> Graph.AxisFormatting.X_DURATION |
|
||||||
else -> Graph.AxisFormatting.DEFAULT |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,84 +0,0 @@ |
|||||||
package net.pokeranalytics.android.calculus.calcul |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import com.github.mikephil.charting.data.* |
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.calculus.ComputedResults |
|
||||||
import net.pokeranalytics.android.calculus.Point |
|
||||||
import net.pokeranalytics.android.calculus.Stat |
|
||||||
import net.pokeranalytics.android.ui.graph.DataSetFactory |
|
||||||
import kotlin.math.abs |
|
||||||
|
|
||||||
|
|
||||||
// MPAndroidChart |
|
||||||
|
|
||||||
fun ComputedResults.defaultStatEntries(stat: Stat, context: Context): DataSet<out Entry> { |
|
||||||
return when (stat) { |
|
||||||
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.HOURLY_DURATION -> this.barEntries(stat, context = context) |
|
||||||
Stat.STANDARD_DEVIATION -> this.distributionEntries(stat, context) |
|
||||||
else -> this.singleLineEntries(stat, context) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fun ComputedResults.singleLineEntries(stat: Stat, context: Context): LineDataSet { |
|
||||||
val entries = mutableListOf<Entry>() |
|
||||||
this.evolutionValues[stat]?.let { points -> |
|
||||||
points.forEachIndexed { index, p -> |
|
||||||
entries.add(Entry(index.toFloat(), p.y.toFloat(), p.data)) |
|
||||||
} |
|
||||||
} |
|
||||||
return DataSetFactory.lineDataSetInstance(entries, this.group.query.getName(context), context) |
|
||||||
} |
|
||||||
|
|
||||||
fun ComputedResults.durationEntries(stat: Stat, context: Context): LineDataSet { |
|
||||||
val entries = mutableListOf<Entry>() |
|
||||||
this.evolutionValues[stat]?.let { points -> |
|
||||||
points.forEach { p -> |
|
||||||
entries.add(Entry(p.x.toFloat(), p.y.toFloat(), p.data)) |
|
||||||
} |
|
||||||
} |
|
||||||
return DataSetFactory.lineDataSetInstance(entries, stat.name, context) |
|
||||||
} |
|
||||||
|
|
||||||
private fun ComputedResults.barEntries(stat: Stat, context: Context): BarDataSet { |
|
||||||
|
|
||||||
val entries = mutableListOf<BarEntry>() |
|
||||||
this.evolutionValues[stat]?.let { points -> |
|
||||||
points.forEach { p -> |
|
||||||
entries.add(BarEntry(p.x.toFloat(), p.y.toFloat(), p.data)) |
|
||||||
} |
|
||||||
} |
|
||||||
return DataSetFactory.barDataSetInstance(entries, stat.name, context) |
|
||||||
} |
|
||||||
|
|
||||||
private fun ComputedResults.distributionEntries(stat: Stat, context: Context): BarDataSet { |
|
||||||
|
|
||||||
val colors = mutableListOf<Int>() |
|
||||||
val entries = mutableListOf<BarEntry>() |
|
||||||
this.evolutionValues[stat]?.let { points -> |
|
||||||
|
|
||||||
val negative = mutableListOf<Point>() |
|
||||||
val positive = mutableListOf<Point>() |
|
||||||
|
|
||||||
points.sortByDescending { it.y } |
|
||||||
points.forEach { |
|
||||||
if (it.y < 0) { |
|
||||||
negative.add(it) |
|
||||||
} else { |
|
||||||
positive.add(it) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
negative.forEachIndexed { index, p -> |
|
||||||
entries.add(BarEntry(index.toFloat(), abs(p.y.toFloat()), p.data)) |
|
||||||
colors.add(context.getColor(R.color.red)) |
|
||||||
} |
|
||||||
positive.forEachIndexed { index, p -> |
|
||||||
val x = negative.size + index.toFloat() |
|
||||||
entries.add(BarEntry(x, p.y.toFloat(), p.data)) |
|
||||||
colors.add(context.getColor(R.color.green)) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
return DataSetFactory.barDataSetInstance(entries, stat.name, context, colors) |
|
||||||
} |
|
||||||
@ -1,64 +0,0 @@ |
|||||||
package net.pokeranalytics.android.calculus.calcul |
|
||||||
|
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.calculus.Calculator |
|
||||||
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
|
||||||
import net.pokeranalytics.android.ui.activity.ComparisonReportActivity |
|
||||||
import net.pokeranalytics.android.ui.activity.ProgressReportActivity |
|
||||||
import net.pokeranalytics.android.ui.activity.TableReportActivity |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
|
||||||
|
|
||||||
/** |
|
||||||
* The way the computed stats are going to be displayed |
|
||||||
*/ |
|
||||||
enum class ReportDisplay : RowRepresentable { |
|
||||||
TABLE, |
|
||||||
PROGRESS, |
|
||||||
COMPARISON, |
|
||||||
MAP; |
|
||||||
|
|
||||||
override val resId: Int? |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
TABLE -> R.string.table |
|
||||||
PROGRESS -> R.string.progress |
|
||||||
COMPARISON -> R.string.comparison |
|
||||||
MAP -> R.string.map |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val activityClass: Class<*> |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
TABLE -> TableReportActivity::class.java |
|
||||||
PROGRESS -> ProgressReportActivity::class.java |
|
||||||
COMPARISON -> ComparisonReportActivity::class.java |
|
||||||
else -> throw PAIllegalStateException("undefined activity for report display") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val progressValues: Calculator.Options.ProgressValues |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
PROGRESS, COMPARISON -> Calculator.Options.ProgressValues.STANDARD |
|
||||||
else -> Calculator.Options.ProgressValues.NONE |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// val requireProgressValues: Boolean |
|
||||||
// get() { |
|
||||||
// return when (this) { |
|
||||||
// PROGRESS, COMPARISON -> true |
|
||||||
// else -> false |
|
||||||
// } |
|
||||||
// } |
|
||||||
|
|
||||||
val multipleStatSelection: Boolean |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
PROGRESS -> false |
|
||||||
else -> true |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,64 +0,0 @@ |
|||||||
package net.pokeranalytics.android.calculus.calcul |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import com.github.mikephil.charting.data.BarDataSet |
|
||||||
import com.github.mikephil.charting.data.BarEntry |
|
||||||
import com.github.mikephil.charting.data.Entry |
|
||||||
import com.github.mikephil.charting.data.LineDataSet |
|
||||||
import net.pokeranalytics.android.calculus.Report |
|
||||||
import net.pokeranalytics.android.calculus.Stat |
|
||||||
import net.pokeranalytics.android.ui.graph.DataSetFactory |
|
||||||
import net.pokeranalytics.android.util.ColorUtils |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* Returns the list of entries corresponding to the provided [stat] |
|
||||||
* One value will be returned by result |
|
||||||
*/ |
|
||||||
fun Report.lineEntries(stat: Stat? = null, context: Context): LineDataSet { |
|
||||||
val entries = mutableListOf<Entry>() |
|
||||||
val statToUse = stat ?: this.options.stats.firstOrNull() |
|
||||||
val statName = statToUse?.name ?: "" |
|
||||||
|
|
||||||
statToUse?.let { |
|
||||||
this.results.forEachIndexed { index, results -> |
|
||||||
results.computedStat(it)?.progressValue?.let { progressValue -> |
|
||||||
entries.add(Entry(index.toFloat(), progressValue.toFloat(), results)) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return DataSetFactory.lineDataSetInstance(entries, statName, context) |
|
||||||
} |
|
||||||
|
|
||||||
fun Report.barEntries(stat: Stat? = null, context: Context): BarDataSet { |
|
||||||
val entries = mutableListOf<BarEntry>() |
|
||||||
val statToUse = stat ?: options.stats.firstOrNull() |
|
||||||
|
|
||||||
statToUse?.let { |
|
||||||
this.results.forEachIndexed { index, results -> |
|
||||||
val cs = results.computedStat(it) |
|
||||||
cs?.let { computedStat -> |
|
||||||
val barEntry = BarEntry(index.toFloat(), computedStat.value.toFloat(), results) |
|
||||||
entries.add(barEntry) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val label = statToUse?.name ?: "" |
|
||||||
return DataSetFactory.barDataSetInstance(entries, label, context) |
|
||||||
} |
|
||||||
|
|
||||||
fun Report.multiLineEntries(context: Context): List<LineDataSet> { |
|
||||||
val dataSets = mutableListOf<LineDataSet>() |
|
||||||
|
|
||||||
options.stats.forEach { stat -> |
|
||||||
this.results.forEachIndexed { index, result -> |
|
||||||
val ds = result.singleLineEntries(stat, context) |
|
||||||
ds.color = ColorUtils.almostRandomColor(index, context) |
|
||||||
dataSets.add(ds) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return dataSets |
|
||||||
} |
|
||||||
@ -1,81 +0,0 @@ |
|||||||
package net.pokeranalytics.android.calculus.calcul |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.calculus.Stat |
|
||||||
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
|
||||||
import net.pokeranalytics.android.ui.view.RowViewType |
|
||||||
import net.pokeranalytics.android.util.NULL_TEXT |
|
||||||
|
|
||||||
class StatRepresentable(var stat: Stat) : RowRepresentable { |
|
||||||
|
|
||||||
companion object { |
|
||||||
fun resId(stat: Stat): Int { |
|
||||||
return when (stat) { |
|
||||||
Stat.NET_RESULT -> R.string.net_result |
|
||||||
Stat.BB_NET_RESULT -> R.string.total_net_result_bb_ |
|
||||||
Stat.HOURLY_RATE -> R.string.average_hour_rate |
|
||||||
Stat.AVERAGE -> R.string.average |
|
||||||
Stat.NUMBER_OF_SETS -> R.string.number_of_sessions |
|
||||||
Stat.NUMBER_OF_GAMES -> R.string.number_of_records |
|
||||||
Stat.HOURLY_DURATION -> R.string.duration |
|
||||||
Stat.AVERAGE_HOURLY_DURATION -> R.string.average_hours_played |
|
||||||
Stat.NET_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands |
|
||||||
Stat.HOURLY_RATE_BB -> R.string.average_hour_rate_bb_ |
|
||||||
Stat.AVERAGE_NET_BB -> R.string.average_net_result_bb_ |
|
||||||
Stat.WIN_RATIO -> R.string.win_ratio |
|
||||||
Stat.AVERAGE_BUYIN -> R.string.average_buyin |
|
||||||
Stat.ROI -> R.string.tournament_roi |
|
||||||
Stat.STANDARD_DEVIATION -> R.string.standard_deviation |
|
||||||
Stat.STANDARD_DEVIATION_HOURLY -> R.string.standard_deviation_per_hour |
|
||||||
Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.standard_deviation_bb_per_100_hands |
|
||||||
Stat.STANDARD_DEVIATION_BB -> R.string.bb_standard_deviation |
|
||||||
Stat.HANDS_PLAYED -> R.string.number_of_hands |
|
||||||
Stat.LOCATIONS_PLAYED -> R.string.locations_played |
|
||||||
Stat.LONGEST_STREAKS -> R.string.longest_streaks |
|
||||||
Stat.MAXIMUM_NET_RESULT -> R.string.max_net_result |
|
||||||
Stat.MINIMUM_NET_RESULT -> R.string.min_net_result |
|
||||||
Stat.MAXIMUM_DURATION -> R.string.longest_session |
|
||||||
Stat.DAYS_PLAYED -> R.string.days_played |
|
||||||
Stat.TOTAL_BUYIN -> R.string.total_buyin |
|
||||||
else -> throw PAIllegalStateException("Stat ${stat.name} name required but undefined") |
|
||||||
} |
|
||||||
} |
|
||||||
fun localizedTitle(stat: Stat, context: Context): String { |
|
||||||
return context.getString(resId(stat)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override val resId: Int |
|
||||||
get() { |
|
||||||
return resId(this.stat) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns a label used to display the legend right value, typically a total or an average |
|
||||||
*/ |
|
||||||
fun cumulativeLabelResId(context: Context): String { |
|
||||||
val resId = when (this.stat) { |
|
||||||
Stat.AVERAGE, Stat.AVERAGE_HOURLY_DURATION, Stat.NET_BB_PER_100_HANDS, |
|
||||||
Stat.HOURLY_RATE_BB, Stat.AVERAGE_NET_BB, Stat.ROI, Stat.HOURLY_RATE -> R.string.average |
|
||||||
Stat.NUMBER_OF_SETS -> R.string.number_of_sessions |
|
||||||
Stat.NUMBER_OF_GAMES -> R.string.number_of_records |
|
||||||
Stat.NET_RESULT -> R.string.total |
|
||||||
Stat.STANDARD_DEVIATION -> R.string.net_result |
|
||||||
Stat.STANDARD_DEVIATION_BB -> R.string.average_net_result_bb_ |
|
||||||
Stat.STANDARD_DEVIATION_HOURLY -> R.string.hour_rate_without_pauses |
|
||||||
Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands |
|
||||||
Stat.WIN_RATIO, Stat.HOURLY_DURATION -> return this.localizedTitle(context) |
|
||||||
else -> null |
|
||||||
} |
|
||||||
resId?.let { |
|
||||||
return context.getString(it) |
|
||||||
} ?: run { |
|
||||||
return NULL_TEXT |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override val viewType: Int = RowViewType.TITLE_VALUE.ordinal |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,38 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model |
|
||||||
|
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.model.realm.PerformanceKey |
|
||||||
import net.pokeranalytics.android.util.enumerations.IntIdentifiable |
|
||||||
import net.pokeranalytics.android.util.enumerations.IntSearchable |
|
||||||
|
|
||||||
enum class LiveOnline(override var uniqueIdentifier: Int) : PerformanceKey, IntIdentifiable { |
|
||||||
LIVE(0), |
|
||||||
ONLINE(1); |
|
||||||
|
|
||||||
companion object : IntSearchable<LiveOnline> { |
|
||||||
|
|
||||||
override fun valuesInternal(): Array<LiveOnline> { |
|
||||||
return values() |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
override val resId: Int? |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
LIVE -> R.string.live |
|
||||||
ONLINE -> R.string.online |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override val value: Int |
|
||||||
get() { |
|
||||||
return this.uniqueIdentifier |
|
||||||
} |
|
||||||
|
|
||||||
val isLive: Boolean |
|
||||||
get() { |
|
||||||
return (this == LIVE) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,13 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model |
|
||||||
|
|
||||||
data class Stakes(var blinds: String?, var ante: Double?) { |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} |
|
||||||
@ -1,13 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.blogpost |
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName |
|
||||||
|
|
||||||
class BlogPost { |
|
||||||
|
|
||||||
@SerializedName("id") |
|
||||||
var id: Int = 0 |
|
||||||
|
|
||||||
@SerializedName("level") |
|
||||||
var content: String = "" |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,108 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.handhistory |
|
||||||
|
|
||||||
import io.realm.Realm |
|
||||||
import net.pokeranalytics.android.model.realm.Game |
|
||||||
import net.pokeranalytics.android.model.realm.Session |
|
||||||
import net.pokeranalytics.android.model.realm.handhistory.HandHistory |
|
||||||
import net.pokeranalytics.android.util.extensions.findById |
|
||||||
import timber.log.Timber |
|
||||||
|
|
||||||
class HandSetup { |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
/*** |
|
||||||
* Returns a HandSetup instance using a [configurationId], the id of a Realm object, |
|
||||||
* Session or HandHistory, used to later configure the HandHistory |
|
||||||
*/ |
|
||||||
fun from(configurationId: String?, attached: Boolean, realm: Realm): HandSetup { |
|
||||||
|
|
||||||
return if (configurationId != null) { |
|
||||||
val handSetup = HandSetup() |
|
||||||
val hh = realm.findById(HandHistory::class.java, configurationId) |
|
||||||
if (hh != null) { |
|
||||||
handSetup.configure(hh) |
|
||||||
} |
|
||||||
val session = realm.findById(Session::class.java, configurationId) |
|
||||||
if (session != null) { |
|
||||||
handSetup.configure(session, attached) |
|
||||||
} |
|
||||||
handSetup |
|
||||||
} else { |
|
||||||
HandSetup() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
var type: Session.Type? = null |
|
||||||
|
|
||||||
var blinds: String? = null |
|
||||||
|
|
||||||
// var bigBlind: Double? = null |
|
||||||
|
|
||||||
var ante: Double? = null |
|
||||||
|
|
||||||
var tableSize: Int? = null |
|
||||||
|
|
||||||
var bigBlindAnte: Boolean = false |
|
||||||
|
|
||||||
var game: Game? = null |
|
||||||
|
|
||||||
var session: Session? = null |
|
||||||
|
|
||||||
var straddlePositions: MutableList<Position> = mutableListOf() |
|
||||||
private set |
|
||||||
|
|
||||||
fun clearStraddles() { |
|
||||||
this.straddlePositions.clear() |
|
||||||
} |
|
||||||
|
|
||||||
private fun configure(handHistory: HandHistory) { |
|
||||||
this.blinds = handHistory.blinds |
|
||||||
this.bigBlindAnte = handHistory.bigBlindAnte |
|
||||||
this.ante = handHistory.ante |
|
||||||
this.tableSize = handHistory.numberOfPlayers |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* Configures the Hand Setup with a [session] |
|
||||||
* [attached] denotes if the HandHistory must be directly linked to the session |
|
||||||
*/ |
|
||||||
private fun configure(session: Session, attached: Boolean) { |
|
||||||
if (attached) { |
|
||||||
this.session = session |
|
||||||
} |
|
||||||
if (session.endDate == null) { |
|
||||||
this.game = session.game // we don't want to force the max number of cards if unsure |
|
||||||
} |
|
||||||
this.type = session.sessionType |
|
||||||
this.blinds = session.cgBlinds |
|
||||||
this.ante = session.cgAnte |
|
||||||
this.tableSize = session.tableSize |
|
||||||
|
|
||||||
val blindValues = session.blindValues |
|
||||||
if (blindValues.size > 2) { |
|
||||||
this.straddlePositions = Position.positionsPerPlayers(10).drop(2).take(blindValues.size - 2).toMutableList() |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* This method sorts the straddle positions in their natural order |
|
||||||
* If the straddle position contains the button, we're usually in a Mississipi straddle, |
|
||||||
* meaning the BUT straddles, then CO, then HJ... |
|
||||||
* Except if it goes to UTG, in which case we don't know if we're in standard straddle, or Mississipi |
|
||||||
* We use the first straddled position to sort out this case |
|
||||||
*/ |
|
||||||
fun setStraddlePositions(firstStraddlePosition: Position, positions: LinkedHashSet<Position>) { |
|
||||||
var sortedPosition = positions.sortedBy { it.ordinal } |
|
||||||
if (positions.contains(Position.BUT) && firstStraddlePosition != Position.UTG) { |
|
||||||
sortedPosition = sortedPosition.reversed() |
|
||||||
} |
|
||||||
Timber.d("sortedPosition = $sortedPosition") |
|
||||||
this.straddlePositions = sortedPosition.toMutableList() |
|
||||||
Timber.d("this.straddlePositions = ${this.straddlePositions}") |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,53 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.handhistory |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
enum class Position(var value: String) : RowRepresentable { |
|
||||||
SB("SB"), |
|
||||||
BB("BB"), |
|
||||||
UTG("UTG"), |
|
||||||
UTG1("UTG+1"), |
|
||||||
UTG2("UTG+2"), |
|
||||||
UTG3("UTG+3"), |
|
||||||
MP("MP"), |
|
||||||
HJ("HJ"), |
|
||||||
CO("CO"), |
|
||||||
BUT("BUT"); |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
fun positionsPerPlayers(playerCount: Int) : LinkedHashSet<Position> { |
|
||||||
return when(playerCount) { |
|
||||||
2 -> linkedSetOf(SB, BB) |
|
||||||
3 -> linkedSetOf(SB, BB, BUT) |
|
||||||
4 -> linkedSetOf(SB, BB, UTG, BUT) |
|
||||||
5 -> linkedSetOf(SB, BB, UTG, CO, BUT) |
|
||||||
6 -> linkedSetOf(SB, BB, UTG, HJ, CO, BUT) |
|
||||||
7 -> linkedSetOf(SB, BB, UTG, MP, HJ, CO, BUT) |
|
||||||
8 -> linkedSetOf(SB, BB, UTG, UTG1, MP, HJ, CO, BUT) |
|
||||||
9 -> linkedSetOf(SB, BB, UTG, UTG1, UTG2, MP, HJ, CO, BUT) |
|
||||||
10 -> linkedSetOf(SB, BB, UTG, UTG1, UTG2, UTG3, MP, HJ, CO, BUT) |
|
||||||
else -> throw PAIllegalStateException("Unmanaged number of players") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
val shortValue: String |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
UTG1 -> "+1" |
|
||||||
UTG2 -> "+2" |
|
||||||
UTG3 -> "+3" |
|
||||||
else -> this.value |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override fun getDisplayName(context: Context): String { |
|
||||||
return this.value |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,50 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.handhistory |
|
||||||
|
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.ui.modules.handhistory.replayer.HandStep |
|
||||||
|
|
||||||
enum class Street : HandStep { |
|
||||||
PREFLOP, |
|
||||||
FLOP, |
|
||||||
TURN, |
|
||||||
RIVER, |
|
||||||
SUMMARY; |
|
||||||
|
|
||||||
override val street: Street = this |
|
||||||
|
|
||||||
val totalBoardCards: Int |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
PREFLOP -> 0 |
|
||||||
FLOP -> 3 |
|
||||||
TURN -> 4 |
|
||||||
RIVER, SUMMARY -> 5 |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val resId: Int |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
PREFLOP -> R.string.street_preflop |
|
||||||
FLOP -> R.string.street_flop |
|
||||||
TURN -> R.string.street_turn |
|
||||||
RIVER -> R.string.street_river |
|
||||||
SUMMARY -> R.string.summary |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val next: Street |
|
||||||
get() { |
|
||||||
return values()[this.ordinal + 1] |
|
||||||
} |
|
||||||
|
|
||||||
val previous: Street? |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
PREFLOP -> null |
|
||||||
else -> values()[this.ordinal - 1] |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@ -1,189 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.interfaces |
|
||||||
|
|
||||||
import net.pokeranalytics.android.model.realm.Bankroll |
|
||||||
import net.pokeranalytics.android.util.BLIND_SEPARATOR |
|
||||||
import net.pokeranalytics.android.util.NULL_TEXT |
|
||||||
import net.pokeranalytics.android.util.UserDefaults |
|
||||||
import net.pokeranalytics.android.util.extensions.formatted |
|
||||||
import net.pokeranalytics.android.util.extensions.toCurrency |
|
||||||
import java.lang.Integer.min |
|
||||||
import java.text.NumberFormat |
|
||||||
import java.text.ParseException |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
data class CodedStake(var stakes: String) : Comparable<CodedStake> { |
|
||||||
|
|
||||||
var ante: Double? = null |
|
||||||
var blinds: String? = null |
|
||||||
var currency: Currency |
|
||||||
|
|
||||||
init { |
|
||||||
|
|
||||||
var currencyCode: String? = null |
|
||||||
|
|
||||||
val parameters = this.stakes.split(StakesHolder.cbSeparator) |
|
||||||
parameters.forEach { param -> |
|
||||||
when { |
|
||||||
param.contains(StakesHolder.cbAnte) -> ante = param.removePrefix(StakesHolder.cbAnte).let { NumberFormat.getInstance().parse(it)?.toDouble() } |
|
||||||
param.contains(StakesHolder.cbBlinds) -> blinds = param.removePrefix(StakesHolder.cbBlinds) |
|
||||||
param.contains(StakesHolder.cbCode) -> currencyCode = param.removePrefix( |
|
||||||
StakesHolder.cbCode |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
this.currency = currencyCode?.let { Currency.getInstance(it) } |
|
||||||
?: run { UserDefaults.currency } |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
override fun compareTo(other: CodedStake): Int { |
|
||||||
|
|
||||||
if (this.currency == other.currency) { |
|
||||||
|
|
||||||
this.blinds?.let { b1 -> |
|
||||||
other.blinds?.let { b2 -> |
|
||||||
if (b1 == b2) { |
|
||||||
return this.compareAnte(other) |
|
||||||
} else { |
|
||||||
val bv1 = this.reversedBlindsArray(b1) |
|
||||||
val bv2 = this.reversedBlindsArray(b2) |
|
||||||
|
|
||||||
for (i in 0 until min(bv1.size, bv2.size)) { |
|
||||||
if (bv1[i] != bv2[i]) { |
|
||||||
return bv1[i].compareTo(bv2[i]) |
|
||||||
} else { |
|
||||||
continue |
|
||||||
} |
|
||||||
} |
|
||||||
return bv1.size.compareTo(bv2.size) |
|
||||||
} |
|
||||||
} ?: run { |
|
||||||
return 1 |
|
||||||
} |
|
||||||
|
|
||||||
} ?: run { |
|
||||||
return this.compareAnte(other) |
|
||||||
} |
|
||||||
} else { |
|
||||||
return this.currency.currencyCode.compareTo(other.currency.currencyCode) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
private fun compareAnte(other: CodedStake): Int { |
|
||||||
this.ante?.let { a1 -> |
|
||||||
other.ante?.let { a2 -> |
|
||||||
return a1.compareTo(a2) |
|
||||||
} ?: run { |
|
||||||
return 1 |
|
||||||
} |
|
||||||
} ?: run { |
|
||||||
return -1 |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private fun reversedBlindsArray(blinds: String): List<Double> { |
|
||||||
return blinds.split(BLIND_SEPARATOR).mapNotNull { NumberFormat.getInstance().parse(it)?.toDouble() }.reversed() |
|
||||||
} |
|
||||||
|
|
||||||
fun formattedStakes(): String { |
|
||||||
val components = arrayListOf<String>() |
|
||||||
this.formattedBlinds()?.let { components.add(it) } |
|
||||||
|
|
||||||
if ((this.ante ?: -1.0) > 0.0) { |
|
||||||
this.formattedAnte()?.let { components.add("($it)") } |
|
||||||
} |
|
||||||
|
|
||||||
return if (components.isNotEmpty()) { |
|
||||||
components.joinToString(" ") |
|
||||||
} else { |
|
||||||
NULL_TEXT |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private fun formattedBlinds(): String? { |
|
||||||
this.blinds?.let { |
|
||||||
val placeholder = 1.0 |
|
||||||
val regex = Regex("-?\\d+(\\.\\d+)?") |
|
||||||
return placeholder.toCurrency(currency).replace(regex, it) |
|
||||||
} |
|
||||||
return null |
|
||||||
} |
|
||||||
|
|
||||||
private fun formattedAnte(): String? { |
|
||||||
this.ante?.let { |
|
||||||
return it.toCurrency(this.currency) |
|
||||||
} |
|
||||||
return null |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
interface StakesHolder { |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
const val cbSeparator = ";" |
|
||||||
const val cbAnte = "A=" |
|
||||||
const val cbBlinds = "B=" |
|
||||||
const val cbCode = "C=" |
|
||||||
|
|
||||||
fun readableStakes(value: String): String { |
|
||||||
return CodedStake(value).formattedStakes() |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
val ante: Double? |
|
||||||
val blinds: String? |
|
||||||
val biggestBet: Double? |
|
||||||
val stakes: String? |
|
||||||
|
|
||||||
val bankroll: Bankroll? |
|
||||||
|
|
||||||
fun setHolderStakes(stakes: String?) |
|
||||||
fun setHolderBiggestBet(biggestBet: Double?) |
|
||||||
|
|
||||||
val blindValues: List<Double> |
|
||||||
get() { |
|
||||||
this.blinds?.let { blinds -> |
|
||||||
val blindsSplit = blinds.split(BLIND_SEPARATOR) |
|
||||||
return blindsSplit.mapNotNull { |
|
||||||
try { |
|
||||||
NumberFormat.getInstance().parse(it)?.toDouble() |
|
||||||
} catch (e: ParseException) { |
|
||||||
null |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return listOf() |
|
||||||
} |
|
||||||
|
|
||||||
fun generateStakes() { |
|
||||||
|
|
||||||
if (this.ante == null && this.blinds == null) { |
|
||||||
setHolderStakes(null) |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
val components = arrayListOf<String>() |
|
||||||
|
|
||||||
this.blinds?.let { components.add("${cbBlinds}${it}") } |
|
||||||
this.ante?.let { components.add("${cbAnte}${it.formatted}") } |
|
||||||
|
|
||||||
val code = this.bankroll?.currency?.code ?: UserDefaults.currency.currencyCode |
|
||||||
components.add("${cbCode}${code}") |
|
||||||
|
|
||||||
setHolderStakes(components.joinToString(cbSeparator)) |
|
||||||
} |
|
||||||
|
|
||||||
fun defineHighestBet() { |
|
||||||
val bets = arrayListOf<Double>() |
|
||||||
this.ante?.let { bets.add(it) } |
|
||||||
bets.addAll(this.blindValues) |
|
||||||
setHolderBiggestBet(bets.maxOrNull()) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -0,0 +1,16 @@ |
|||||||
|
package net.pokeranalytics.android.model.realm |
||||||
|
|
||||||
|
import io.realm.RealmObject |
||||||
|
import io.realm.annotations.PrimaryKey |
||||||
|
import java.util.* |
||||||
|
|
||||||
|
|
||||||
|
open class HandHistory : RealmObject() { |
||||||
|
|
||||||
|
@PrimaryKey |
||||||
|
var id = UUID.randomUUID().toString() |
||||||
|
|
||||||
|
// the date of the hand history |
||||||
|
var date: Date = Date() |
||||||
|
|
||||||
|
} |
||||||
@ -1,72 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.realm |
|
||||||
|
|
||||||
import io.realm.Realm |
|
||||||
import io.realm.RealmObject |
|
||||||
import io.realm.annotations.PrimaryKey |
|
||||||
import net.pokeranalytics.android.calculus.Stat |
|
||||||
import net.pokeranalytics.android.model.LiveOnline |
|
||||||
import net.pokeranalytics.android.ui.view.rows.StaticReport |
|
||||||
import net.pokeranalytics.android.util.NULL_TEXT |
|
||||||
import net.pokeranalytics.android.util.extensions.lookupForNameInAllTablesById |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
|
|
||||||
interface PerformanceKey { |
|
||||||
val resId: Int? |
|
||||||
val value: Int |
|
||||||
} |
|
||||||
|
|
||||||
open class Performance() : RealmObject() { |
|
||||||
|
|
||||||
@PrimaryKey |
|
||||||
var id: String = UUID.randomUUID().toString() |
|
||||||
|
|
||||||
constructor( |
|
||||||
report: StaticReport, |
|
||||||
key: PerformanceKey, |
|
||||||
name: String? = null, |
|
||||||
objectId: String? = null, |
|
||||||
customFieldId: String? = null, |
|
||||||
value: Double? = null |
|
||||||
) : this() { |
|
||||||
|
|
||||||
this.reportId = report.uniqueIdentifier |
|
||||||
this.key = key.value |
|
||||||
this.name = name |
|
||||||
this.objectId = objectId |
|
||||||
this.customFieldId = customFieldId |
|
||||||
this.value = value |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
var reportId: Int = 0 |
|
||||||
var key: Int = 0 |
|
||||||
var name: String? = null |
|
||||||
var objectId: String? = null |
|
||||||
var customFieldId: String? = null |
|
||||||
var value: Double? = null |
|
||||||
|
|
||||||
fun toStaticReport(realm: Realm): StaticReport { |
|
||||||
return StaticReport.newInstance(realm, this.reportId, this.customFieldId) |
|
||||||
} |
|
||||||
|
|
||||||
fun displayValue(realm: Realm): CharSequence { |
|
||||||
this.name?.let { return it } |
|
||||||
this.objectId?.let { realm.lookupForNameInAllTablesById(it) } |
|
||||||
return NULL_TEXT |
|
||||||
} |
|
||||||
|
|
||||||
val stat: Stat |
|
||||||
get() { |
|
||||||
return Stat.valueByIdentifier(this.key.toInt()) |
|
||||||
} |
|
||||||
|
|
||||||
val resId: Int? |
|
||||||
get() { |
|
||||||
return when (this.reportId) { |
|
||||||
StaticReport.OptimalDuration.uniqueIdentifier -> LiveOnline.valueByIdentifier(this.key).resId |
|
||||||
else -> stat.resId |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,41 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.realm |
|
||||||
|
|
||||||
import io.realm.Realm |
|
||||||
import io.realm.RealmObject |
|
||||||
import io.realm.annotations.PrimaryKey |
|
||||||
import net.pokeranalytics.android.util.UUID_SEPARATOR |
|
||||||
import net.pokeranalytics.android.util.extensions.findById |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
open class UserConfig : RealmObject() { |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
fun getConfiguration(realm: Realm): UserConfig { |
|
||||||
realm.where(UserConfig::class.java).findFirst()?.let { config -> |
|
||||||
return config |
|
||||||
} |
|
||||||
return UserConfig() |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@PrimaryKey |
|
||||||
var id = UUID.randomUUID().toString() |
|
||||||
|
|
||||||
var liveDealtHandsPerHour: Int = 250 |
|
||||||
|
|
||||||
var onlineDealtHandsPerHour: Int = 500 |
|
||||||
|
|
||||||
var transactionTypeIds: String = "" |
|
||||||
|
|
||||||
fun setTransactionTypeIds(transactionTypes: Set<TransactionType>) { |
|
||||||
this.transactionTypeIds = transactionTypes.joinToString(UUID_SEPARATOR) { it.id } |
|
||||||
} |
|
||||||
|
|
||||||
fun transactionTypes(realm: Realm): List<TransactionType> { |
|
||||||
val ids = this.transactionTypeIds.split(UUID_SEPARATOR) |
|
||||||
return ids.mapNotNull { realm.findById(it) } |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,252 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.realm.handhistory |
|
||||||
|
|
||||||
import io.realm.RealmObject |
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
|
||||||
import net.pokeranalytics.android.model.handhistory.Position |
|
||||||
import net.pokeranalytics.android.model.handhistory.Street |
|
||||||
import net.pokeranalytics.android.ui.modules.handhistory.model.ActionReadRow |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
|
||||||
import net.pokeranalytics.android.ui.view.RowViewType |
|
||||||
import net.pokeranalytics.android.util.extensions.formatted |
|
||||||
|
|
||||||
|
|
||||||
/*** |
|
||||||
* An extension to transform a list of ComputedAction into |
|
||||||
* a more compact and read-friendly list of ActionReadRow |
|
||||||
*/ |
|
||||||
fun List<Action>.compact(positions: LinkedHashSet<Position>, heroIndex: Int?): List<ActionReadRow> { |
|
||||||
val rows = mutableListOf<ActionReadRow>() |
|
||||||
this.forEach { |
|
||||||
if (it.type == Action.Type.FOLD && rows.lastOrNull()?.action == Action.Type.FOLD) { |
|
||||||
rows.lastOrNull()?.positions?.add(positions.elementAt(it.position)) |
|
||||||
} else { |
|
||||||
rows.add(it.toReadRow(positions, heroIndex, null)) // TODO stack. The method is used for text export only atm |
|
||||||
} |
|
||||||
} |
|
||||||
return rows |
|
||||||
} |
|
||||||
|
|
||||||
fun Action.toReadRow(positions: LinkedHashSet<Position>, heroIndex: Int?, stack: Double?): ActionReadRow { |
|
||||||
val pos = positions.elementAt(this.position) |
|
||||||
val isHero = (heroIndex == this.position) |
|
||||||
|
|
||||||
var amount = this.amount |
|
||||||
if (this.type?.isCall == true) { |
|
||||||
amount = this.effectiveAmount |
|
||||||
} |
|
||||||
|
|
||||||
return ActionReadRow(mutableListOf(pos), this.position, this.type, amount, stack, isHero) |
|
||||||
} |
|
||||||
|
|
||||||
open class Action : RealmObject() { |
|
||||||
|
|
||||||
enum class Type(override var resId: Int) : RowRepresentable { |
|
||||||
|
|
||||||
POST_SB(R.string.posts_sb), |
|
||||||
POST_BB(R.string.post_bb), |
|
||||||
STRADDLE(R.string.straddle), |
|
||||||
FOLD(R.string.fold), |
|
||||||
CHECK(R.string.check), |
|
||||||
CALL(R.string.call), |
|
||||||
BET(R.string.bet), |
|
||||||
POT(R.string.pot), |
|
||||||
RAISE(R.string.raise), |
|
||||||
UNDEFINED_ALLIN(R.string.allin), |
|
||||||
CALL_ALLIN(R.string.callin), |
|
||||||
BET_ALLIN(R.string.ballin), |
|
||||||
RAISE_ALLIN(R.string.rallin); |
|
||||||
|
|
||||||
val isBlind: Boolean |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
POST_SB, POST_BB -> true |
|
||||||
else -> false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val isSignificant: Boolean |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
POST_SB, POST_BB, STRADDLE, BET, POT, RAISE, BET_ALLIN, RAISE_ALLIN -> true |
|
||||||
UNDEFINED_ALLIN -> throw PAIllegalStateException("Can't ask for UNDEFINED_ALLIN") |
|
||||||
else -> false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* Tells if the action pulls the player out from any new decision |
|
||||||
*/ |
|
||||||
val isPullOut: Boolean |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
FOLD, BET_ALLIN, RAISE_ALLIN, CALL_ALLIN, UNDEFINED_ALLIN -> true |
|
||||||
else -> false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* Returns if the action is passive |
|
||||||
*/ |
|
||||||
val isPassive: Boolean |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
FOLD, CHECK -> true |
|
||||||
else -> false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val isCall: Boolean |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
CALL, CALL_ALLIN -> true |
|
||||||
else -> false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val isAllin: Boolean |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
UNDEFINED_ALLIN, BET_ALLIN, RAISE_ALLIN, CALL_ALLIN -> true |
|
||||||
else -> false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val color: Int |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
POST_SB, POST_BB, CALL, CHECK, CALL_ALLIN -> R.color.kaki_lighter |
|
||||||
FOLD -> R.color.red |
|
||||||
else -> R.color.green |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val background: Int |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
POST_SB, POST_BB, CALL, CHECK, CALL_ALLIN -> R.drawable.rounded_kaki_medium_rect |
|
||||||
FOLD -> R.drawable.rounded_red_rect |
|
||||||
else -> R.drawable.rounded_green_rect |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override val viewType: Int = RowViewType.TITLE_GRID.ordinal |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
val defaultTypes: List<Type> by lazy { |
|
||||||
listOf(FOLD, CHECK, BET, POT, CALL, RAISE, UNDEFINED_ALLIN) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* The street of the action |
|
||||||
*/ |
|
||||||
private var streetIdentifier: Int = 0 |
|
||||||
var street: Street |
|
||||||
set(value) { |
|
||||||
this.streetIdentifier = value.ordinal |
|
||||||
} |
|
||||||
get() { |
|
||||||
return streetIdentifier.let { Street.values()[it] } |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* The index of the action |
|
||||||
*/ |
|
||||||
var index: Int = 0 |
|
||||||
|
|
||||||
/*** |
|
||||||
* The position of the user making the action |
|
||||||
*/ |
|
||||||
var position: Int = 0 |
|
||||||
|
|
||||||
/*** |
|
||||||
* The type of action: check, fold, raise... |
|
||||||
*/ |
|
||||||
private var typeIdentifier: Int? = null |
|
||||||
|
|
||||||
var type: Type? |
|
||||||
set(value) { |
|
||||||
this.typeIdentifier = value?.ordinal |
|
||||||
} |
|
||||||
get() { |
|
||||||
return typeIdentifier?.let { Type.values()[it] } |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* The amount linked for a bet, raise... |
|
||||||
*/ |
|
||||||
var amount: Double? = null |
|
||||||
set(value) { |
|
||||||
field = value |
|
||||||
// Timber.d("/// set value = $value") |
|
||||||
} |
|
||||||
|
|
||||||
var effectiveAmount: Double = 0.0 |
|
||||||
|
|
||||||
var positionRemainingStack: Double? = null |
|
||||||
|
|
||||||
val isActionSignificant: Boolean |
|
||||||
get() { |
|
||||||
return this.type?.isSignificant ?: false |
|
||||||
} |
|
||||||
|
|
||||||
val formattedAmount: String? |
|
||||||
get() { |
|
||||||
val amount = when (this.type) { |
|
||||||
Type.CALL, Type.CALL_ALLIN -> this.effectiveAmount |
|
||||||
else -> this.amount |
|
||||||
} |
|
||||||
return amount?.formatted |
|
||||||
} |
|
||||||
|
|
||||||
fun toggleType(remainingStack: Double) { |
|
||||||
|
|
||||||
when (this.type) { |
|
||||||
Type.BET -> { |
|
||||||
if (remainingStack == 0.0) { |
|
||||||
this.type = Type.BET_ALLIN |
|
||||||
} |
|
||||||
} |
|
||||||
Type.BET_ALLIN -> { |
|
||||||
if (remainingStack > 0.0) { |
|
||||||
this.type = Type.BET |
|
||||||
} |
|
||||||
} |
|
||||||
Type.RAISE -> { |
|
||||||
if (remainingStack == 0.0) { |
|
||||||
this.type = Type.RAISE_ALLIN |
|
||||||
} |
|
||||||
} |
|
||||||
Type.RAISE_ALLIN -> { |
|
||||||
if (remainingStack > 0.0) { |
|
||||||
this.type = Type.RAISE |
|
||||||
} |
|
||||||
} |
|
||||||
Type.CALL -> { |
|
||||||
if (remainingStack == 0.0) { |
|
||||||
this.type = Type.CALL_ALLIN |
|
||||||
} |
|
||||||
} |
|
||||||
Type.CALL_ALLIN -> { |
|
||||||
if (remainingStack > 0.0) { |
|
||||||
this.type = Type.CALL |
|
||||||
} |
|
||||||
} |
|
||||||
else -> {} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val displayedAmount: Double? |
|
||||||
get() { |
|
||||||
if (this.type?.isCall == true) { |
|
||||||
return this.effectiveAmount |
|
||||||
} |
|
||||||
return this.amount |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,775 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.realm.handhistory |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import android.view.LayoutInflater |
|
||||||
import android.view.View |
|
||||||
import android.view.ViewGroup |
|
||||||
import androidx.appcompat.widget.AppCompatTextView |
|
||||||
import io.realm.Realm |
|
||||||
import io.realm.RealmList |
|
||||||
import io.realm.RealmObject |
|
||||||
import io.realm.annotations.Ignore |
|
||||||
import io.realm.annotations.PrimaryKey |
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.model.filter.Filterable |
|
||||||
import net.pokeranalytics.android.model.handhistory.HandSetup |
|
||||||
import net.pokeranalytics.android.model.handhistory.Position |
|
||||||
import net.pokeranalytics.android.model.handhistory.Street |
|
||||||
import net.pokeranalytics.android.model.interfaces.* |
|
||||||
import net.pokeranalytics.android.model.realm.Bankroll |
|
||||||
import net.pokeranalytics.android.model.realm.Session |
|
||||||
import net.pokeranalytics.android.ui.modules.handhistory.evaluator.EvaluatorBridge |
|
||||||
import net.pokeranalytics.android.ui.modules.handhistory.model.ActionReadRow |
|
||||||
import net.pokeranalytics.android.ui.modules.handhistory.model.CardHolder |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
|
||||||
import net.pokeranalytics.android.ui.view.RowViewType |
|
||||||
import net.pokeranalytics.android.util.extensions.addLineReturn |
|
||||||
import net.pokeranalytics.android.util.extensions.formatted |
|
||||||
import net.pokeranalytics.android.util.extensions.fullDate |
|
||||||
import timber.log.Timber |
|
||||||
import java.util.* |
|
||||||
import kotlin.math.max |
|
||||||
|
|
||||||
data class PositionAmount(var position: Int, var amount: Double, var isAllin: Boolean) |
|
||||||
|
|
||||||
open class HandHistory : RealmObject(), Deletable, RowRepresentable, Filterable, TimeFilterable, |
|
||||||
CardHolder, Comparator<PositionAmount>, StakesHolder { |
|
||||||
|
|
||||||
@PrimaryKey |
|
||||||
override var id = UUID.randomUUID().toString() |
|
||||||
|
|
||||||
@Ignore |
|
||||||
override val realmObjectClass: Class<out Identifiable> = HandHistory::class.java |
|
||||||
|
|
||||||
/*** |
|
||||||
* The date of the hand history |
|
||||||
*/ |
|
||||||
var date: Date = Date() |
|
||||||
set(value) { |
|
||||||
field = value |
|
||||||
this.updateTimeParameter(value) |
|
||||||
} |
|
||||||
|
|
||||||
init { |
|
||||||
this.date = Date() // force the custom setter call |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* The session whose hand was played |
|
||||||
*/ |
|
||||||
var session: Session? = null |
|
||||||
|
|
||||||
/*** |
|
||||||
* The small blind |
|
||||||
*/ |
|
||||||
var oldSmallBlind: Double? = null |
|
||||||
set(value) { |
|
||||||
field = value |
|
||||||
// if (this.bigBlind == null && value != null) { |
|
||||||
// this.bigBlind = value * 2 |
|
||||||
// } |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* The big blind |
|
||||||
*/ |
|
||||||
var oldBigBlind: Double? = null |
|
||||||
set(value) { |
|
||||||
field = value |
|
||||||
// if (this.smallBlind == null && value != null) { |
|
||||||
// this.smallBlind = value / 2 |
|
||||||
// } |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* Big blind ante |
|
||||||
*/ |
|
||||||
var bigBlindAnte: Boolean = false |
|
||||||
|
|
||||||
/*** |
|
||||||
* The ante |
|
||||||
*/ |
|
||||||
override var ante: Double? = 0.0 |
|
||||||
set(value) { |
|
||||||
field = value |
|
||||||
this.generateStakes() |
|
||||||
this.defineHighestBet() |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* The blinds |
|
||||||
*/ |
|
||||||
override var blinds: String? = null |
|
||||||
set(value) { |
|
||||||
field = value |
|
||||||
this.generateStakes() |
|
||||||
this.defineHighestBet() |
|
||||||
} |
|
||||||
|
|
||||||
override var biggestBet: Double? = null |
|
||||||
|
|
||||||
/*** |
|
||||||
* The coded stakes |
|
||||||
*/ |
|
||||||
override var stakes: String? = null |
|
||||||
|
|
||||||
override val bankroll: Bankroll? |
|
||||||
get() { return this.session?.bankroll } |
|
||||||
|
|
||||||
override fun setHolderStakes(stakes: String?) { |
|
||||||
this.stakes = stakes |
|
||||||
} |
|
||||||
|
|
||||||
override fun setHolderBiggestBet(biggestBet: Double?) { |
|
||||||
this.biggestBet = biggestBet |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* Number of players in the hand |
|
||||||
*/ |
|
||||||
var numberOfPlayers: Int = 9 |
|
||||||
|
|
||||||
/*** |
|
||||||
* Number of players in the hand |
|
||||||
*/ |
|
||||||
var comment: String? = null |
|
||||||
|
|
||||||
/*** |
|
||||||
* The position index of the hero |
|
||||||
*/ |
|
||||||
var heroIndex: Int? = null |
|
||||||
|
|
||||||
/*** |
|
||||||
* Indicates if the hero wins the hand |
|
||||||
*/ |
|
||||||
var winnerPots: RealmList<WonPot> = RealmList() |
|
||||||
|
|
||||||
/*** |
|
||||||
* The board |
|
||||||
*/ |
|
||||||
var board: RealmList<Card> = RealmList() |
|
||||||
|
|
||||||
/*** |
|
||||||
* The players actions |
|
||||||
*/ |
|
||||||
var actions: RealmList<Action> = RealmList() |
|
||||||
|
|
||||||
/*** |
|
||||||
* A list of players initial data: user, position, hand, stack |
|
||||||
*/ |
|
||||||
var playerSetups: RealmList<PlayerSetup> = RealmList() |
|
||||||
|
|
||||||
// Timed interface |
|
||||||
override var dayOfWeek: Int? = null |
|
||||||
override var month: Int? = null |
|
||||||
override var year: Int? = null |
|
||||||
override var dayOfMonth: Int? = null |
|
||||||
|
|
||||||
/*** |
|
||||||
* Returns the indexes of all players |
|
||||||
*/ |
|
||||||
val positionIndexes: IntRange |
|
||||||
get() { return (0 until this.numberOfPlayers) } |
|
||||||
|
|
||||||
// Deletable |
|
||||||
|
|
||||||
override fun isValidForDelete(realm: Realm): Boolean { |
|
||||||
return true |
|
||||||
} |
|
||||||
|
|
||||||
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int { |
|
||||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. |
|
||||||
} |
|
||||||
|
|
||||||
override fun deleteDependencies(realm: Realm) { |
|
||||||
this.board.deleteAllFromRealm() |
|
||||||
this.playerSetups.deleteAllFromRealm() |
|
||||||
this.actions.deleteAllFromRealm() |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* Configures a hand history with a [handSetup] |
|
||||||
*/ |
|
||||||
fun configure(handSetup: HandSetup, keepPlayers: Boolean = false) { |
|
||||||
|
|
||||||
if (!keepPlayers) { |
|
||||||
this.playerSetups.removeAll(this.playerSetups) |
|
||||||
} |
|
||||||
|
|
||||||
handSetup.tableSize?.let { this.numberOfPlayers = it } |
|
||||||
handSetup.ante?.let { this.ante = it } |
|
||||||
handSetup.blinds?.let { this.blinds = it } |
|
||||||
|
|
||||||
this.session = handSetup.session |
|
||||||
this.date = this.session?.handHistoryAutomaticDate ?: Date() |
|
||||||
|
|
||||||
this.createActions(handSetup) |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* Creates the initial actions of the hand history |
|
||||||
*/ |
|
||||||
private fun createActions(handSetup: HandSetup) { |
|
||||||
|
|
||||||
this.actions.clear() |
|
||||||
|
|
||||||
var blindValues = this.blindValues |
|
||||||
if (blindValues.isNotEmpty()) { |
|
||||||
blindValues.forEachIndexed { index, blind -> |
|
||||||
val action = when(index) { |
|
||||||
0 -> Action.Type.POST_SB |
|
||||||
1 -> Action.Type.POST_BB |
|
||||||
else -> null |
|
||||||
} |
|
||||||
action?.let { this.addAction(index, action, blind) } |
|
||||||
} |
|
||||||
} else { |
|
||||||
this.addAction(0, Action.Type.POST_SB, this.oldSmallBlind) |
|
||||||
this.addAction(1, Action.Type.POST_BB, this.oldBigBlind) |
|
||||||
} |
|
||||||
|
|
||||||
blindValues = blindValues.drop(2) |
|
||||||
val positions = Position.positionsPerPlayers(this.numberOfPlayers) |
|
||||||
handSetup.straddlePositions.forEachIndexed { index, position -> // position are sorted here |
|
||||||
val positionIndex = positions.indexOf(position) |
|
||||||
val amount = if (index < blindValues.size) { blindValues[index] } else null |
|
||||||
this.addAction(positionIndex, Action.Type.STRADDLE, amount) |
|
||||||
} |
|
||||||
|
|
||||||
// var lastStraddler: Int? = null |
|
||||||
|
|
||||||
|
|
||||||
// val totalActions = this.actions.size |
|
||||||
// val startingPosition = lastStraddler?.let { it + 1 } ?: totalActions |
|
||||||
|
|
||||||
// for (i in this.positionIndexes - 1) { // we don't add the BB / straddler by default in case of a walk |
|
||||||
// this.addAction((startingPosition + i) % this.numberOfPlayers) |
|
||||||
// } |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* Adds an action with the given [position], [type] and [amount] to the actions list |
|
||||||
*/ |
|
||||||
private fun addAction(position: Int, type: Action.Type? = null, amount: Double? = null) { |
|
||||||
val action = Action() |
|
||||||
action.index = this.actions.size |
|
||||||
action.position = position |
|
||||||
action.type = type |
|
||||||
action.amount = amount |
|
||||||
action.effectiveAmount = amount ?: 0.0 |
|
||||||
this.actions.add(action) |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* Returns the board cards for a given [street] |
|
||||||
*/ |
|
||||||
fun cardsForStreet(street: Street): MutableList<Card> { |
|
||||||
return this.board.sortedBy { it.index }.take(street.totalBoardCards).toMutableList() |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* Returns the optional PlayerSetup object at the [position] |
|
||||||
*/ |
|
||||||
fun playerSetupForPosition(position: Int): PlayerSetup? { |
|
||||||
return this.playerSetups.firstOrNull { it.position == position } |
|
||||||
} |
|
||||||
|
|
||||||
override val cards: RealmList<Card> |
|
||||||
get() { return this.board } |
|
||||||
|
|
||||||
/*** |
|
||||||
* Returns the ante sum |
|
||||||
*/ |
|
||||||
val anteSum: Double |
|
||||||
get() { |
|
||||||
return if (bigBlindAnte) { |
|
||||||
this.biggestBet ?: 0.0 |
|
||||||
} else { |
|
||||||
(this.ante ?: 0.0) * this.numberOfPlayers |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* Returns the sorted list of actions by index |
|
||||||
*/ |
|
||||||
private val sortedActions: List<Action> |
|
||||||
get() { |
|
||||||
return this.actions.sortedBy { it.index } |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* Returns the list of undefined positions, |
|
||||||
* meaning the positions where no PlayerSetup has been created |
|
||||||
*/ |
|
||||||
fun undefinedPositions(): List<Position> { |
|
||||||
val positions = Position.positionsPerPlayers(this.numberOfPlayers) |
|
||||||
val copy = positions.clone() as LinkedHashSet<Position> |
|
||||||
this.playerSetups.forEach { |
|
||||||
copy.remove(positions.elementAt(it.position)) |
|
||||||
} |
|
||||||
return copy.toList() |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* Creates and affect a PlayerSetup at the given [positionIndex] |
|
||||||
*/ |
|
||||||
fun createPlayerSetup(positionIndex: Int): PlayerSetup { |
|
||||||
|
|
||||||
val playerSetup = if (this.realm != null) { |
|
||||||
this.realm.createObject(PlayerSetup::class.java) } |
|
||||||
else { |
|
||||||
PlayerSetup() |
|
||||||
} |
|
||||||
|
|
||||||
playerSetup.position = positionIndex |
|
||||||
this.playerSetups.add(playerSetup) |
|
||||||
return playerSetup |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* Returns the pot size at the start of the given [street] |
|
||||||
*/ |
|
||||||
fun potSizeForStreet(street: Street): Double { |
|
||||||
val sortedActions = this.sortedActions |
|
||||||
val firstIndexOfStreet = sortedActions.firstOrNull { it.street == street }?.index |
|
||||||
?: sortedActions.size |
|
||||||
return this.anteSum + sortedActions.take(firstIndexOfStreet).sumOf { it.effectiveAmount } |
|
||||||
} |
|
||||||
|
|
||||||
@Ignore |
|
||||||
override val viewType: Int = RowViewType.HAND_HISTORY.ordinal |
|
||||||
|
|
||||||
override fun localizedString(context: Context): CharSequence { |
|
||||||
|
|
||||||
val positions = Position.positionsPerPlayers(this.numberOfPlayers) |
|
||||||
|
|
||||||
var string = "" |
|
||||||
|
|
||||||
// Settings |
|
||||||
val players = "${this.numberOfPlayers} ${context.getString(R.string.players)}" |
|
||||||
val firstLineComponents = mutableListOf(this.date.fullDate(), players) |
|
||||||
|
|
||||||
this.blinds?.let { firstLineComponents.add(it) } |
|
||||||
|
|
||||||
// this.smallBlind?.let { sb -> |
|
||||||
// this.bigBlind?.let { bb -> |
|
||||||
// firstLineComponents.add("${sb.formatted}/${bb.formatted}") |
|
||||||
// } |
|
||||||
// } |
|
||||||
|
|
||||||
this.ante?.let { |
|
||||||
if (it > 0.0) { |
|
||||||
firstLineComponents.add("ante ${this.ante}") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
string = string.plus(firstLineComponents.joinToString(" - ")) |
|
||||||
string = string.addLineReturn(2) |
|
||||||
|
|
||||||
// Comment |
|
||||||
this.comment?.let { comment -> |
|
||||||
string = string.plus(comment) |
|
||||||
string = string.addLineReturn(2) |
|
||||||
} |
|
||||||
|
|
||||||
// Players |
|
||||||
this.playerSetups.sortedBy { it.position }.forEach { |
|
||||||
string = string.plus(localizedPlayerSetup(it, positions, context)) |
|
||||||
string = string.addLineReturn() |
|
||||||
} |
|
||||||
|
|
||||||
// Actions per street |
|
||||||
val sortedActions = this.actions.sortedBy { it.index } |
|
||||||
|
|
||||||
// Remove SUMMARY |
|
||||||
Street.values().dropLast(1).forEach { street -> |
|
||||||
|
|
||||||
string = string.addLineReturn(2) |
|
||||||
|
|
||||||
val streetActions = sortedActions.filter { it.street == street }.compact(positions, this.heroIndex) |
|
||||||
|
|
||||||
val streetCards = this.cardsForStreet(street) |
|
||||||
|
|
||||||
// we want to show streets: with actions or when an allin is called |
|
||||||
if (streetCards.size == street.totalBoardCards || streetActions.isNotEmpty()) { |
|
||||||
|
|
||||||
val streetItems = mutableListOf<CharSequence>(context.getString(street.resId)) |
|
||||||
|
|
||||||
val potSize = this.potSizeForStreet(street) |
|
||||||
if (potSize > 0) { |
|
||||||
streetItems.add("(" + potSize.formatted + ")") |
|
||||||
} |
|
||||||
string = string.plus(streetItems.joinToString(" ")) |
|
||||||
|
|
||||||
if (streetCards.isNotEmpty()) { |
|
||||||
string = string.addLineReturn() |
|
||||||
string = string.plus(streetCards.formatted(context) ?: "") |
|
||||||
} |
|
||||||
string = string.addLineReturn() |
|
||||||
string = string.plus("-----------") |
|
||||||
|
|
||||||
string = string.addLineReturn() |
|
||||||
|
|
||||||
streetActions.forEach { action -> |
|
||||||
string = string.plus(localizedAction(action, context)) |
|
||||||
string = string.addLineReturn() |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
return string |
|
||||||
} |
|
||||||
|
|
||||||
fun anteForPosition(position: Position): Double { |
|
||||||
return if (this.bigBlindAnte) { |
|
||||||
if (position == Position.BB) { |
|
||||||
this.biggestBet ?: 0.0 |
|
||||||
} else { |
|
||||||
0.0 |
|
||||||
} |
|
||||||
} else { |
|
||||||
this.ante ?: 0.0 |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* Returns a string representation of the [playerSetup] |
|
||||||
*/ |
|
||||||
private fun localizedPlayerSetup(playerSetup: PlayerSetup, positions: LinkedHashSet<Position>, context: Context): String { |
|
||||||
|
|
||||||
val playerItems = mutableListOf(positions.elementAt(playerSetup.position).value) |
|
||||||
|
|
||||||
val isHero = (playerSetup.position == this.heroIndex) |
|
||||||
if (isHero) { |
|
||||||
val heroString = context.getString(R.string.hero) |
|
||||||
playerItems.add("- $heroString") |
|
||||||
} |
|
||||||
|
|
||||||
if (playerSetup.cards.isNotEmpty()) { |
|
||||||
playerItems.add("[${playerSetup.cards.formatted(context)}]") |
|
||||||
} |
|
||||||
|
|
||||||
playerSetup.stack?.let { stack -> |
|
||||||
playerItems.add("- $stack") |
|
||||||
} |
|
||||||
return playerItems.joinToString(" ") |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* Returns a string representation of the [actionReadRow] |
|
||||||
*/ |
|
||||||
private fun localizedAction(actionReadRow: ActionReadRow, context: Context): String { |
|
||||||
val formattedPositions = actionReadRow.positions.joinToString(", ") { it.value } |
|
||||||
val actionItems = mutableListOf(formattedPositions) |
|
||||||
actionReadRow.action?.let { type -> |
|
||||||
actionItems.add(context.getString(type.resId)) |
|
||||||
} |
|
||||||
actionReadRow.amount?.let { amount -> |
|
||||||
actionItems.add(amount.formatted) |
|
||||||
} |
|
||||||
return actionItems.joinToString(" ") |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* Returns if the hero has won the hand, or part of the pot |
|
||||||
*/ |
|
||||||
val heroWins: Boolean? |
|
||||||
get() { |
|
||||||
return this.heroIndex?.let { heroIndex -> |
|
||||||
this.largestWonPot?.let { pot -> |
|
||||||
heroIndex == pot.position |
|
||||||
} ?: run { null } |
|
||||||
|
|
||||||
// heroIndex == this.largestWonPot?.position |
|
||||||
// this.winnerPots.any { it.position == heroIndex } |
|
||||||
} ?: run { |
|
||||||
null |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* Creates a list of cards for the hand history to give a representation of the hand |
|
||||||
* We will try to add a minimum of 5 cards using by priority: |
|
||||||
* - the hero hand |
|
||||||
* - the opponents hands |
|
||||||
* - the board |
|
||||||
*/ |
|
||||||
fun cardViews(context: Context, viewGroup: ViewGroup): List<View> { |
|
||||||
|
|
||||||
val views = mutableListOf<View>() |
|
||||||
val layoutInflater = LayoutInflater.from(context) |
|
||||||
|
|
||||||
// Create all the possible cards list: hero, opponents, board |
|
||||||
val cardSets = mutableListOf<List<Card>>() |
|
||||||
|
|
||||||
// Hero |
|
||||||
this.heroIndex?.let { hIndex -> |
|
||||||
this.playerSetupForPosition(hIndex)?.cards?.let { |
|
||||||
cardSets.add(it) |
|
||||||
} |
|
||||||
} |
|
||||||
// Opponents |
|
||||||
this.playerSetups.filter { it.cards.isNotEmpty() && it.position != this.heroIndex }.forEach { |
|
||||||
cardSets.add(it.cards) |
|
||||||
} |
|
||||||
// Board |
|
||||||
cardSets.add(this.board) |
|
||||||
|
|
||||||
// Try to add cards but not too much |
|
||||||
while (views.size < 5 && cardSets.isNotEmpty()) { |
|
||||||
|
|
||||||
val cardSet = cardSets.removeAt(0) |
|
||||||
|
|
||||||
if (views.isNotEmpty() && cardSet.isNotEmpty()) { // add separator with previous set of cards |
|
||||||
val view = layoutInflater.inflate(R.layout.view_card_separator, viewGroup, false) as AppCompatTextView |
|
||||||
views.add(view) |
|
||||||
} |
|
||||||
cardSet.forEach { views.add(it.view(context, layoutInflater, viewGroup)) } |
|
||||||
} |
|
||||||
|
|
||||||
// Add 5 blank cards if no card has been added |
|
||||||
if (views.isEmpty()) { |
|
||||||
val blankCard = Card() |
|
||||||
(1..5).forEach { _ -> |
|
||||||
val view = blankCard.view(context, layoutInflater, viewGroup, true) |
|
||||||
view.setBackgroundResource(R.drawable.rounded_kaki_medium_rect) |
|
||||||
views.add(view) |
|
||||||
} |
|
||||||
} |
|
||||||
return views |
|
||||||
} |
|
||||||
|
|
||||||
data class Pot(var amount: Double, var level: Double, var positions: MutableSet<Int> = mutableSetOf()) |
|
||||||
|
|
||||||
/*** |
|
||||||
* Defines which positions win the hand |
|
||||||
*/ |
|
||||||
fun defineWinnerPositions() { |
|
||||||
|
|
||||||
val folds = this.sortedActions.filter { it.type == Action.Type.FOLD }.map { it.position } |
|
||||||
val activePositions = this.positionIndexes.toMutableList() |
|
||||||
activePositions.removeAll(folds) |
|
||||||
|
|
||||||
val wonPots = when (activePositions.size) { |
|
||||||
0 -> listOf() // no winner, everyone has fold. Should not happen |
|
||||||
1 -> { // One player has not fold, typically BET / FOLD |
|
||||||
val pot = WonPot() |
|
||||||
pot.position = activePositions.first() |
|
||||||
pot.amount = potSizeForStreet(Street.SUMMARY) |
|
||||||
listOf(pot) |
|
||||||
} |
|
||||||
else -> { // Several players remains, typically BET/FOLD or CHECKS |
|
||||||
this.wonPots(getPots(activePositions)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
this.winnerPots.clear() |
|
||||||
this.winnerPots.addAll(wonPots) |
|
||||||
|
|
||||||
Timber.d("Pot won: ${this.winnerPots.size} for positions: ${this.winnerPots.map {it.position}} ") |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* Returns a list with all the different pots with the appropriate eligible players |
|
||||||
*/ |
|
||||||
fun getPots(eligiblePositions: List<Int>): List<Pot> { |
|
||||||
|
|
||||||
var runningPotAmount = 0.0 |
|
||||||
val pots = mutableListOf<Pot>() |
|
||||||
Street.values().forEach { street -> |
|
||||||
|
|
||||||
val streetActions = this.actions.filter { it.street == street } |
|
||||||
|
|
||||||
val allinPositions = streetActions.filter { it.type?.isAllin == true }.map { it.position } |
|
||||||
|
|
||||||
if (allinPositions.isEmpty()) { |
|
||||||
runningPotAmount += streetActions.sumOf { it.effectiveAmount } |
|
||||||
} else { |
|
||||||
val amountsPerPosition = mutableListOf<PositionAmount>() |
|
||||||
|
|
||||||
// get all committed amounts for the street by player, by allin |
|
||||||
this.positionIndexes.map { position -> |
|
||||||
val playerActions = streetActions.filter { it.position == position } |
|
||||||
val sum = playerActions.sumOf { it.effectiveAmount } |
|
||||||
amountsPerPosition.add(PositionAmount(position, sum, allinPositions.contains(position))) |
|
||||||
} |
|
||||||
amountsPerPosition.sortWith(this) // sort by value, then allin. Allin must be first of equal values sequence |
|
||||||
|
|
||||||
// for each player |
|
||||||
val streetPots = mutableListOf<Pot>() |
|
||||||
amountsPerPosition.forEach { positionAmount -> |
|
||||||
val amount = positionAmount.amount |
|
||||||
val position = positionAmount.position |
|
||||||
|
|
||||||
var rest = amount |
|
||||||
var lastPotLevel = 0.0 |
|
||||||
// put invested amount in smaller pots |
|
||||||
streetPots.forEach { pot -> |
|
||||||
val added = pot.level - lastPotLevel |
|
||||||
pot.amount += added |
|
||||||
if (eligiblePositions.contains(position)) { |
|
||||||
pot.positions.add(position) |
|
||||||
} |
|
||||||
rest -= added |
|
||||||
lastPotLevel = pot.level |
|
||||||
} |
|
||||||
// Adds remaining chips to the running Pot |
|
||||||
runningPotAmount += rest |
|
||||||
|
|
||||||
// If the player is allin, create a new pot for the relevant amount |
|
||||||
val isAllin = allinPositions.contains(position) |
|
||||||
if (isAllin) { |
|
||||||
streetPots.add(Pot(runningPotAmount, amount, mutableSetOf(position))) |
|
||||||
runningPotAmount = 0.0 |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
pots.addAll(streetPots) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
// Create a pot with the remaining chips |
|
||||||
if (runningPotAmount > 0.0) { |
|
||||||
pots.add(Pot(runningPotAmount, 0.0, eligiblePositions.toMutableSet())) |
|
||||||
} |
|
||||||
|
|
||||||
return pots |
|
||||||
} |
|
||||||
|
|
||||||
private fun wonPots(pots: List<Pot>): Collection<WonPot> { |
|
||||||
|
|
||||||
val wonPots = hashMapOf<Int, WonPot>() |
|
||||||
|
|
||||||
pots.forEach { pot -> |
|
||||||
|
|
||||||
if (pot.positions.size > 1) { // we only consider contested pots |
|
||||||
|
|
||||||
val winningPositions = compareHands(pot.positions.toList()) |
|
||||||
|
|
||||||
// Distributes the pot for each winners |
|
||||||
val share = pot.amount / winningPositions.size |
|
||||||
winningPositions.forEach { p -> |
|
||||||
val wp = wonPots[p] |
|
||||||
if (wp == null) { |
|
||||||
val wonPot = WonPot() |
|
||||||
wonPot.position = p |
|
||||||
wonPot.amount = share |
|
||||||
wonPots[p] = wonPot |
|
||||||
} else { |
|
||||||
wp.amount += share |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
return wonPots.values |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* Compares the hands of the players at the given [activePositions] |
|
||||||
* Returns the list of winning hands by position |
|
||||||
*/ |
|
||||||
private fun compareHands(activePositions: List<Int>): List<Int> { |
|
||||||
|
|
||||||
val usedFullCards = this.allFullCards.toMutableList() |
|
||||||
val noWildCardBoard = this.board.putSuits(usedFullCards) |
|
||||||
val noWildCardHands = activePositions.map { |
|
||||||
this.playerSetupForPosition(it)?.cards?.putSuits(usedFullCards) |
|
||||||
} |
|
||||||
|
|
||||||
// Evaluate all hands |
|
||||||
val results = noWildCardHands.map { cards -> |
|
||||||
cards?.let { hand -> |
|
||||||
EvaluatorBridge.playerHand(hand, noWildCardBoard) |
|
||||||
} ?: run { |
|
||||||
Int.MAX_VALUE |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Evaluate all hands |
|
||||||
// val results = activePositions.map { |
|
||||||
// this.playerSetupForPosition(it)?.cards?.let { hand -> |
|
||||||
// EvaluatorBridge.playerHand(hand, this.board) |
|
||||||
// } ?: run { |
|
||||||
// Int.MAX_VALUE |
|
||||||
// } |
|
||||||
// } |
|
||||||
|
|
||||||
// Check who has best score (EvaluatorBridge gives a lowest score for a better hand) |
|
||||||
return results.minOrNull()?.let { best -> |
|
||||||
val winners = mutableListOf<Int>() |
|
||||||
results.forEachIndexed { index, i -> |
|
||||||
if (i == best && i != Int.MAX_VALUE) { |
|
||||||
winners.add(activePositions[index]) |
|
||||||
} |
|
||||||
} |
|
||||||
winners |
|
||||||
} ?: run { |
|
||||||
listOf<Int>() // type needed for build |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* return a negative integer, zero, or a positive integer as the |
|
||||||
* first argument is less than, equal to, or greater than the |
|
||||||
* second. |
|
||||||
*/ |
|
||||||
override fun compare(o1: PositionAmount, o2: PositionAmount): Int { |
|
||||||
return if (o1.amount == o2.amount) { |
|
||||||
if (o1.isAllin) -1 else 1 |
|
||||||
} else { |
|
||||||
(o1.amount - o2.amount).toInt() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val maxPlayerCards: Int |
|
||||||
get() { |
|
||||||
var max = 0 |
|
||||||
this.playerSetups.forEach { |
|
||||||
max = max(it.cards.size, max) |
|
||||||
} |
|
||||||
return max |
|
||||||
} |
|
||||||
|
|
||||||
val usesWildcards: Boolean |
|
||||||
get() { |
|
||||||
val boardHasWildCard = this.cards.any { it.isWildCard } |
|
||||||
val playerCardHasWildCard = this.playerSetups.any { it.cards.any { it.isWildCard } } |
|
||||||
return boardHasWildCard || playerCardHasWildCard |
|
||||||
} |
|
||||||
|
|
||||||
private val allFullCards: List<Card> |
|
||||||
get() { |
|
||||||
val cards = mutableListOf<Card>() |
|
||||||
cards.addAll(this.board) |
|
||||||
this.playerSetups.forEach { |
|
||||||
cards.addAll(it.cards) |
|
||||||
} |
|
||||||
return cards.filter { !it.isWildCard } |
|
||||||
} |
|
||||||
|
|
||||||
val allSuitWildCards: List<Card> |
|
||||||
get() { |
|
||||||
val cards = mutableListOf<Card>() |
|
||||||
cards.addAll(this.board) |
|
||||||
this.playerSetups.forEach { |
|
||||||
cards.addAll(it.cards) |
|
||||||
} |
|
||||||
return cards.filter { it.isSuitWildCard } |
|
||||||
} |
|
||||||
|
|
||||||
private val largestWonPot: WonPot? |
|
||||||
get() { |
|
||||||
return if (this.winnerPots.isNotEmpty()) { // needed, otherwise maxBy crashes |
|
||||||
this.winnerPots.maxBy { it.amount } |
|
||||||
} else { |
|
||||||
null |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,31 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.realm.handhistory |
|
||||||
|
|
||||||
import io.realm.RealmList |
|
||||||
import io.realm.RealmObject |
|
||||||
import net.pokeranalytics.android.model.realm.Player |
|
||||||
import net.pokeranalytics.android.ui.modules.handhistory.model.CardHolder |
|
||||||
import net.pokeranalytics.android.ui.view.Localizable |
|
||||||
|
|
||||||
open class PlayerSetup : RealmObject(), CardHolder, Localizable { |
|
||||||
|
|
||||||
/*** |
|
||||||
* The player |
|
||||||
*/ |
|
||||||
var player: Player? = null |
|
||||||
|
|
||||||
/*** |
|
||||||
* The position at the table |
|
||||||
*/ |
|
||||||
var position: Int = 0 |
|
||||||
|
|
||||||
/*** |
|
||||||
* The initial stack of the player |
|
||||||
*/ |
|
||||||
var stack: Double? = null |
|
||||||
|
|
||||||
/*** |
|
||||||
* The cards of the player |
|
||||||
*/ |
|
||||||
override var cards: RealmList<Card> = RealmList() |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,17 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.realm.handhistory |
|
||||||
|
|
||||||
import io.realm.RealmObject |
|
||||||
|
|
||||||
open class WonPot: RealmObject() { |
|
||||||
|
|
||||||
/*** |
|
||||||
* The position of the player |
|
||||||
*/ |
|
||||||
var position: Int = 0 |
|
||||||
|
|
||||||
/*** |
|
||||||
* The amount won |
|
||||||
*/ |
|
||||||
var amount: Double = 0.0 |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,14 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.realm.rows |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import net.pokeranalytics.android.model.realm.Bankroll |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
|
||||||
|
|
||||||
class BankrollRow(var bankroll: Bankroll) : RowRepresentable { |
|
||||||
|
|
||||||
override fun getDisplayName(context: Context): String { |
|
||||||
return this.bankroll.name |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
} |
|
||||||
@ -1,57 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.realm.rows |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import android.text.InputType |
|
||||||
import io.realm.RealmList |
|
||||||
import io.realm.annotations.Ignore |
|
||||||
import net.pokeranalytics.android.model.realm.CustomField |
|
||||||
import net.pokeranalytics.android.model.realm.CustomFieldEntry |
|
||||||
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor |
|
||||||
import net.pokeranalytics.android.ui.view.RowViewType |
|
||||||
|
|
||||||
class CustomFieldRow(var customField: CustomField) : RowRepresentable { |
|
||||||
|
|
||||||
@Ignore |
|
||||||
override var viewType: Int = RowViewType.TITLE_VALUE_ARROW.ordinal |
|
||||||
|
|
||||||
override fun localizedTitle(context: Context): String { |
|
||||||
return this.customField.name |
|
||||||
} |
|
||||||
|
|
||||||
override fun getDisplayName(context: Context): String { |
|
||||||
return this.customField.name |
|
||||||
} |
|
||||||
|
|
||||||
override val bottomSheetType: BottomSheetType |
|
||||||
get() { |
|
||||||
return when (this.customField.type) { |
|
||||||
CustomField.Type.LIST.uniqueIdentifier -> BottomSheetType.LIST_STATIC |
|
||||||
else -> BottomSheetType.NUMERIC_TEXT |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override fun editingDescriptors(map: Map<String, Any?>): ArrayList<RowRepresentableEditDescriptor> { |
|
||||||
return when (this.customField.type) { |
|
||||||
CustomField.Type.LIST.uniqueIdentifier -> { |
|
||||||
val defaultValue: Any? by map |
|
||||||
val data: RealmList<CustomFieldEntry>? by map |
|
||||||
arrayListOf( |
|
||||||
RowRepresentableEditDescriptor(defaultValue, staticData = data) |
|
||||||
) |
|
||||||
} |
|
||||||
else -> { |
|
||||||
val defaultValue: Double? by map |
|
||||||
arrayListOf( |
|
||||||
RowRepresentableEditDescriptor( |
|
||||||
defaultValue, inputType = InputType.TYPE_CLASS_NUMBER |
|
||||||
or InputType.TYPE_NUMBER_FLAG_DECIMAL |
|
||||||
or InputType.TYPE_NUMBER_FLAG_SIGNED |
|
||||||
) |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -0,0 +1,11 @@ |
|||||||
|
package net.pokeranalytics.android.model.retrofit |
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName |
||||||
|
|
||||||
|
/** |
||||||
|
* CurrencyCode Converter mapping class |
||||||
|
*/ |
||||||
|
class CurrencyConverterValue { |
||||||
|
@SerializedName("val") |
||||||
|
var value: Double? = 0.0 |
||||||
|
} |
||||||
@ -1,17 +1,21 @@ |
|||||||
package net.pokeranalytics.android.ui.modules.bankroll |
package net.pokeranalytics.android.ui.activity |
||||||
|
|
||||||
import android.content.Context |
import android.content.Context |
||||||
import android.content.Intent |
import android.content.Intent |
||||||
import android.os.Bundle |
import android.os.Bundle |
||||||
import androidx.fragment.app.Fragment |
import androidx.fragment.app.Fragment |
||||||
|
import io.realm.RealmResults |
||||||
import net.pokeranalytics.android.R |
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.model.realm.Bankroll |
||||||
|
import net.pokeranalytics.android.model.realm.ComputableResult |
||||||
|
import net.pokeranalytics.android.model.realm.Transaction |
||||||
import net.pokeranalytics.android.ui.activity.components.BaseActivity |
import net.pokeranalytics.android.ui.activity.components.BaseActivity |
||||||
|
|
||||||
class BankrollActivity : BaseActivity() { |
class BankrollActivity : BaseActivity() { |
||||||
|
|
||||||
// private lateinit var computableResults: RealmResults<ComputableResult> |
private lateinit var computableResults: RealmResults<ComputableResult> |
||||||
// private lateinit var bankrolls: RealmResults<Bankroll> |
private lateinit var bankrolls: RealmResults<Bankroll> |
||||||
// private lateinit var transactions: RealmResults<Transaction> |
private lateinit var transactions: RealmResults<Transaction> |
||||||
|
|
||||||
companion object { |
companion object { |
||||||
fun newInstance(context: Context) { |
fun newInstance(context: Context) { |
||||||
@ -0,0 +1,58 @@ |
|||||||
|
package net.pokeranalytics.android.ui.activity |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.content.Intent |
||||||
|
import android.os.Bundle |
||||||
|
import androidx.fragment.app.Fragment |
||||||
|
import kotlinx.android.synthetic.main.activity_data_list.* |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.ui.activity.components.BaseActivity |
||||||
|
import net.pokeranalytics.android.ui.fragment.DataListFragment |
||||||
|
import net.pokeranalytics.android.ui.interfaces.FilterActivityRequestCode |
||||||
|
|
||||||
|
class DataListActivity : BaseActivity() { |
||||||
|
|
||||||
|
enum class IntentKey(val keyName: String) { |
||||||
|
DATA_TYPE("DATA_TYPE"), |
||||||
|
LIVE_DATA_TYPE("LIVE_DATA_TYPE"), |
||||||
|
ITEM_DELETED("ITEM_DELETED"), |
||||||
|
SHOW_ADD_BUTTON("SHOW_ADD_BUTTON"), |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
fun newInstance(context: Context, dataType: Int) { |
||||||
|
context.startActivity(getIntent(context, dataType)) |
||||||
|
} |
||||||
|
|
||||||
|
fun newSelectInstance(fragment: Fragment, dataType: Int, showAddButton: Boolean = true) { |
||||||
|
val context = fragment.requireContext() |
||||||
|
fragment.startActivityForResult(getIntent(context, dataType, showAddButton), FilterActivityRequestCode.SELECT_FILTER.ordinal) |
||||||
|
} |
||||||
|
|
||||||
|
private fun getIntent(context: Context, dataType: Int, showAddButton: Boolean = true): Intent { |
||||||
|
val intent = Intent(context, DataListActivity::class.java) |
||||||
|
intent.putExtra(IntentKey.DATA_TYPE.keyName, dataType) |
||||||
|
intent.putExtra(IntentKey.SHOW_ADD_BUTTON.keyName, showAddButton) |
||||||
|
return intent |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) { |
||||||
|
super.onCreate(savedInstanceState) |
||||||
|
setContentView(R.layout.activity_data_list) |
||||||
|
|
||||||
|
initUI() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Init UI |
||||||
|
*/ |
||||||
|
private fun initUI() { |
||||||
|
val dataType = intent.getIntExtra(IntentKey.DATA_TYPE.keyName, 0) |
||||||
|
val showAddButton = intent.getBooleanExtra(IntentKey.SHOW_ADD_BUTTON.keyName, true) |
||||||
|
val fragment = dataListFragment as DataListFragment |
||||||
|
fragment.setData(dataType) |
||||||
|
fragment.updateUI(showAddButton) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -1,99 +0,0 @@ |
|||||||
package net.pokeranalytics.android.ui.activity |
|
||||||
|
|
||||||
import android.Manifest |
|
||||||
import android.content.Context |
|
||||||
import android.content.Intent |
|
||||||
import android.net.Uri |
|
||||||
import android.os.Bundle |
|
||||||
import androidx.fragment.app.FragmentActivity |
|
||||||
import io.realm.Realm |
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
|
||||||
import net.pokeranalytics.android.ui.activity.components.BaseActivity |
|
||||||
import net.pokeranalytics.android.ui.activity.components.RequestCode |
|
||||||
import net.pokeranalytics.android.ui.activity.components.ResultCode |
|
||||||
import net.pokeranalytics.android.ui.extensions.showAlertDialog |
|
||||||
import net.pokeranalytics.android.ui.extensions.toast |
|
||||||
import net.pokeranalytics.android.util.copyStreamToFile |
|
||||||
import timber.log.Timber |
|
||||||
import java.io.File |
|
||||||
|
|
||||||
class DatabaseCopyActivity : BaseActivity() { |
|
||||||
|
|
||||||
private lateinit var fileURI: Uri |
|
||||||
|
|
||||||
enum class IntentKey(val keyName: String) { |
|
||||||
URI("uri") |
|
||||||
} |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
/** |
|
||||||
* Create a new instance for result |
|
||||||
*/ |
|
||||||
fun newInstanceForResult(context: FragmentActivity, uri: Uri) { |
|
||||||
context.startActivityForResult(getIntent(context, uri), RequestCode.IMPORT.value) |
|
||||||
} |
|
||||||
|
|
||||||
private fun getIntent(context: Context, uri: Uri): Intent { |
|
||||||
Timber.d("getIntent") |
|
||||||
val intent = Intent(context, DatabaseCopyActivity::class.java) |
|
||||||
intent.putExtra(IntentKey.URI.keyName, uri) |
|
||||||
return intent |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) { |
|
||||||
super.onCreate(savedInstanceState) |
|
||||||
|
|
||||||
Timber.d("onCreate") |
|
||||||
|
|
||||||
intent?.data?.let { |
|
||||||
this.fileURI = it |
|
||||||
} ?: run { |
|
||||||
this.fileURI = intent.getParcelableExtra(IntentKey.URI.keyName) ?: throw PAIllegalStateException("Uri not found") |
|
||||||
} |
|
||||||
|
|
||||||
// setContentView(R.layout.activity_import) |
|
||||||
requestImportConfirmation() |
|
||||||
} |
|
||||||
|
|
||||||
private fun initUI() { |
|
||||||
|
|
||||||
askForPermission(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), RequestCode.PERMISSION_WRITE_EXTERNAL_STORAGE.value) { |
|
||||||
val path = Realm.getDefaultInstance().path |
|
||||||
contentResolver.openInputStream(fileURI)?.let { inputStream -> |
|
||||||
val destination = File(path) |
|
||||||
inputStream.copyStreamToFile(destination) |
|
||||||
toast("Please restart app") |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { |
|
||||||
super.onActivityResult(requestCode, resultCode, data) |
|
||||||
|
|
||||||
when (requestCode) { |
|
||||||
RequestCode.IMPORT.value -> { |
|
||||||
if (resultCode == ResultCode.IMPORT_UNRECOGNIZED_FORMAT.value) { |
|
||||||
showAlertDialog(context = this, messageResId = R.string.unknown_import_format_popup_message, positiveAction = { |
|
||||||
finish() |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Import |
|
||||||
|
|
||||||
private fun requestImportConfirmation() { |
|
||||||
|
|
||||||
showAlertDialog(context = this, title = R.string.import_confirmation, showCancelButton = true, positiveAction = { |
|
||||||
initUI() |
|
||||||
}, negativeAction = { |
|
||||||
finish() |
|
||||||
}) |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -0,0 +1,71 @@ |
|||||||
|
package net.pokeranalytics.android.ui.activity |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.content.Intent |
||||||
|
import android.os.Bundle |
||||||
|
import androidx.fragment.app.Fragment |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.model.realm.Filter |
||||||
|
import net.pokeranalytics.android.ui.activity.components.BaseActivity |
||||||
|
import net.pokeranalytics.android.ui.fragment.FilterDetailsFragment |
||||||
|
|
||||||
|
class FilterDetailsActivity : BaseActivity() { |
||||||
|
|
||||||
|
enum class IntentKey(val keyName: String) { |
||||||
|
FILTER_ID("FILTER_ID"), |
||||||
|
FILTER_CATEGORY_ORDINAL("FILTER_CATEGORY_ORDINAL") |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
/** |
||||||
|
* Default constructor |
||||||
|
*/ |
||||||
|
fun newInstance(context: Context, filterId: String, filterCategoryOrdinal: Int) { |
||||||
|
val intent = Intent(context, FilterDetailsActivity::class.java) |
||||||
|
intent.putExtra(IntentKey.FILTER_ID.keyName, filterId) |
||||||
|
intent.putExtra(IntentKey.FILTER_CATEGORY_ORDINAL.keyName, filterCategoryOrdinal) |
||||||
|
context.startActivity(intent) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a new instance for result |
||||||
|
*/ |
||||||
|
fun newInstanceForResult(fragment: Fragment, filterId: String, filterCategoryOrdinal: Int, requestCode: Int, filter: Filter? = null) { |
||||||
|
|
||||||
|
val intent = Intent(fragment.requireContext(), FilterDetailsActivity::class.java) |
||||||
|
intent.putExtra(IntentKey.FILTER_ID.keyName, filterId) |
||||||
|
intent.putExtra(IntentKey.FILTER_CATEGORY_ORDINAL.keyName, filterCategoryOrdinal) |
||||||
|
fragment.startActivityForResult(intent, requestCode) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private lateinit var fragment: FilterDetailsFragment |
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) { |
||||||
|
super.onCreate(savedInstanceState) |
||||||
|
setContentView(R.layout.activity_filter_details) |
||||||
|
initUI() |
||||||
|
} |
||||||
|
|
||||||
|
override fun onBackPressed() { |
||||||
|
fragment.onBackPressed() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Init UI |
||||||
|
*/ |
||||||
|
private fun initUI() { |
||||||
|
|
||||||
|
val fragmentManager = supportFragmentManager |
||||||
|
val fragmentTransaction = fragmentManager.beginTransaction() |
||||||
|
val filterId = intent.getStringExtra(IntentKey.FILTER_ID.keyName) |
||||||
|
val filterCategoryOrdinal = intent.getIntExtra(IntentKey.FILTER_CATEGORY_ORDINAL.keyName, 0) |
||||||
|
|
||||||
|
fragment = FilterDetailsFragment() |
||||||
|
fragmentTransaction.add(R.id.container, fragment) |
||||||
|
fragmentTransaction.commit() |
||||||
|
fragment.setData(filterId, filterCategoryOrdinal) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,74 @@ |
|||||||
|
package net.pokeranalytics.android.ui.activity |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.content.Intent |
||||||
|
import android.os.Bundle |
||||||
|
import androidx.fragment.app.Fragment |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.ui.activity.components.BaseActivity |
||||||
|
import net.pokeranalytics.android.ui.fragment.FiltersFragment |
||||||
|
import net.pokeranalytics.android.ui.interfaces.FilterActivityRequestCode |
||||||
|
import net.pokeranalytics.android.ui.interfaces.FilterableType |
||||||
|
|
||||||
|
class FiltersActivity : BaseActivity() { |
||||||
|
|
||||||
|
enum class IntentKey(val keyName: String) { |
||||||
|
FILTER_ID("FILTER_ID"), |
||||||
|
FILTERABLE_TYPE("FILTERABLE_TYPE"), |
||||||
|
HIDE_MOST_USED_FILTERS("HIDE_MOST_USED_FILTERS"), |
||||||
|
; |
||||||
|
} |
||||||
|
|
||||||
|
private lateinit var fragment: FiltersFragment |
||||||
|
|
||||||
|
companion object { |
||||||
|
/** |
||||||
|
* Create a new instance for result |
||||||
|
*/ |
||||||
|
fun newInstanceForResult(fragment: Fragment, filterId: String? = null, currentFilterable: FilterableType, hideMostUsedFilters: Boolean = false) { |
||||||
|
val intent = getIntent(fragment.requireContext(), filterId, currentFilterable, hideMostUsedFilters) |
||||||
|
fragment.startActivityForResult(intent, FilterActivityRequestCode.CREATE_FILTER.ordinal) |
||||||
|
} |
||||||
|
|
||||||
|
private fun getIntent(context: Context, filterId: String?, currentFilterable: FilterableType, hideMostUsedFilters: Boolean = false): Intent { |
||||||
|
val intent = Intent(context, FiltersActivity::class.java) |
||||||
|
intent.putExtra(IntentKey.FILTERABLE_TYPE.keyName, currentFilterable.uniqueIdentifier) |
||||||
|
intent.putExtra(IntentKey.HIDE_MOST_USED_FILTERS.keyName, hideMostUsedFilters) |
||||||
|
filterId?.let { |
||||||
|
intent.putExtra(IntentKey.FILTER_ID.keyName, it) |
||||||
|
} |
||||||
|
return intent |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) { |
||||||
|
super.onCreate(savedInstanceState) |
||||||
|
setContentView(R.layout.activity_filters) |
||||||
|
initUI() |
||||||
|
} |
||||||
|
|
||||||
|
override fun onBackPressed() { |
||||||
|
fragment.onBackPressed() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Init UI |
||||||
|
*/ |
||||||
|
private fun initUI() { |
||||||
|
|
||||||
|
val fragmentManager = supportFragmentManager |
||||||
|
val fragmentTransaction = fragmentManager.beginTransaction() |
||||||
|
val filterId = intent.getStringExtra(IntentKey.FILTER_ID.keyName) |
||||||
|
val uniqueIdentifier= intent.getIntExtra(IntentKey.FILTERABLE_TYPE.keyName, 0) |
||||||
|
val hideMostUsedFilters = intent.getBooleanExtra(IntentKey.HIDE_MOST_USED_FILTERS.keyName, false) |
||||||
|
val filterableType = FilterableType.valueByIdentifier(uniqueIdentifier) |
||||||
|
|
||||||
|
fragment = FiltersFragment() |
||||||
|
fragment.setData(filterId, filterableType) |
||||||
|
fragmentTransaction.add(R.id.container, fragment) |
||||||
|
fragmentTransaction.commit() |
||||||
|
fragment.updateMostUsedFiltersVisibility(!hideMostUsedFilters) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,58 @@ |
|||||||
|
package net.pokeranalytics.android.ui.activity |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.content.Intent |
||||||
|
import android.os.Bundle |
||||||
|
import androidx.fragment.app.Fragment |
||||||
|
import kotlinx.android.synthetic.main.activity_filters_list.* |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.ui.activity.components.BaseActivity |
||||||
|
import net.pokeranalytics.android.ui.fragment.FiltersListFragment |
||||||
|
import net.pokeranalytics.android.ui.interfaces.FilterActivityRequestCode |
||||||
|
|
||||||
|
class FiltersListActivity : BaseActivity() { |
||||||
|
|
||||||
|
enum class IntentKey(val keyName: String) { |
||||||
|
DATA_TYPE("DATA_TYPE"), |
||||||
|
LIVE_DATA_TYPE("LIVE_DATA_TYPE"), |
||||||
|
ITEM_DELETED("ITEM_DELETED"), |
||||||
|
SHOW_ADD_BUTTON("SHOW_ADD_BUTTON"), |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
fun newInstance(context: Context, dataType: Int) { |
||||||
|
context.startActivity(getIntent(context, dataType)) |
||||||
|
} |
||||||
|
|
||||||
|
fun newSelectInstance(fragment: Fragment, dataType: Int, showAddButton: Boolean = true) { |
||||||
|
val context = fragment.requireContext() |
||||||
|
fragment.startActivityForResult(getIntent(context, dataType, showAddButton), FilterActivityRequestCode.SELECT_FILTER.ordinal) |
||||||
|
} |
||||||
|
|
||||||
|
private fun getIntent(context: Context, dataType: Int, showAddButton: Boolean = true): Intent { |
||||||
|
val intent = Intent(context, FiltersListActivity::class.java) |
||||||
|
intent.putExtra(IntentKey.DATA_TYPE.keyName, dataType) |
||||||
|
intent.putExtra(IntentKey.SHOW_ADD_BUTTON.keyName, showAddButton) |
||||||
|
return intent |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) { |
||||||
|
super.onCreate(savedInstanceState) |
||||||
|
setContentView(R.layout.activity_filters_list) |
||||||
|
|
||||||
|
initUI() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Init UI |
||||||
|
*/ |
||||||
|
private fun initUI() { |
||||||
|
val dataType = intent.getIntExtra(IntentKey.DATA_TYPE.keyName, 0) |
||||||
|
val showAddButton = intent.getBooleanExtra(IntentKey.SHOW_ADD_BUTTON.keyName, true) |
||||||
|
val fragment = filtersListFragment as FiltersListFragment |
||||||
|
fragment.setData(dataType) |
||||||
|
fragment.updateUI(showAddButton) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue