New p1nkl0bst3r's API endpoint + TL progress bar design
For some reason TL progress bar behaves differently on android
This commit is contained in:
parent
6195705dcb
commit
9aa67686da
|
@ -756,16 +756,15 @@ class EstTr {
|
|||
srarea = (_apm * 0) + (_pps * 135) + (_vs * 0) + (_app * 290) + (_dss * 0) + (_dsp * 700) + (_gbe * 0);
|
||||
statrank = 11.2 * atan((srarea - 93) / 130) + 1;
|
||||
if (statrank <= 0) statrank = 0.001;
|
||||
estglicko = (4.0867 * srarea + 186.68);
|
||||
//estglicko = (4.0867 * srarea + 186.68);
|
||||
double ntemp = _pps*(150+(((_vs/_apm) - 1.66)*35))+_app*290+_dsp*700;
|
||||
estglicko = 0.000013*pow(ntemp, 3) - 0.0196 *pow(ntemp, 2) + (12.645*ntemp)-1005.4;
|
||||
esttr = 25000 /
|
||||
(
|
||||
1 + pow(10, (
|
||||
(
|
||||
(
|
||||
1500-(
|
||||
0.000013*pow(ntemp, 3) - 0.0196 *pow(ntemp, 2) + (12.645*ntemp)-1005.4
|
||||
)
|
||||
1500-estglicko
|
||||
)*pi
|
||||
)/sqrt(
|
||||
(
|
||||
|
|
|
@ -77,6 +77,7 @@ class TetrioService extends DB {
|
|||
final Map<String, PlayerLeaderboardPosition> _lbPositions = {};
|
||||
final Map<String, List<News>> _newsCache = {};
|
||||
final Map<String, Map<String, double?>> _topTRcache = {};
|
||||
final Map<String, List<Map<String, double>>> _cutoffsCache = {};
|
||||
final Map<String, TetraLeagueAlphaStream> _tlStreamsCache = {};
|
||||
/// Thing, that sends every request to the API endpoints
|
||||
final client = kDebugMode ? UserAgentClient("Kagari-chan loves osk (Tetra Stats dev build)", http.Client()) : UserAgentClient("Tetra Stats v${packageInfo.version} (dm @dan63047 if someone abuse that software)", http.Client());
|
||||
|
@ -297,6 +298,67 @@ class TetrioService extends DB {
|
|||
// Sidenote: as you can see, fetch functions looks and works pretty much same way, as described above,
|
||||
// so i'm going to document only unique differences between them
|
||||
|
||||
Future<List<Map<String, double>>> fetchCutoffs() async {
|
||||
try{
|
||||
var cached = _cutoffsCache.entries.first;
|
||||
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){ // if not expired
|
||||
developer.log("fetchCutoffs: Cutoffs retrieved from cache, that expires ${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)}", name: "services/tetrio_crud");
|
||||
return cached.value;
|
||||
}else{ // if cache expired
|
||||
_topTRcache.remove(cached.key);
|
||||
developer.log("fetchCutoffs: Cutoffs expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
|
||||
}
|
||||
}catch(e){ // actually going to obtain
|
||||
developer.log("fetchCutoffs: Trying to retrieve Cutoffs", name: "services/tetrio_crud");
|
||||
}
|
||||
|
||||
Uri url;
|
||||
if (kIsWeb) {
|
||||
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "PeakTR"});
|
||||
} else {
|
||||
url = Uri.https('api.p1nkl0bst3r.xyz', 'rankcutoff', {"users": null});
|
||||
}
|
||||
|
||||
try{
|
||||
final response = await client.get(url);
|
||||
|
||||
switch (response.statusCode) {
|
||||
case 200:
|
||||
Map<String, dynamic> rawData = jsonDecode(response.body);
|
||||
Map<String, dynamic> data = rawData["cutoffs"] as Map<String, dynamic>;
|
||||
Map<String, double> trCutoffs = {};
|
||||
Map<String, double> glickoCutoffs = {};
|
||||
for (String rank in data.keys){
|
||||
trCutoffs[rank] = data[rank]["rating"];
|
||||
glickoCutoffs[rank] = data[rank]["glicko"];
|
||||
}
|
||||
_cutoffsCache[(rawData["ts"] + 300000).toString()] = [trCutoffs, glickoCutoffs];
|
||||
return [trCutoffs, glickoCutoffs];
|
||||
case 404:
|
||||
developer.log("fetchCutoffs: Cutoffs are gone", name: "services/tetrio_crud", error: response.statusCode);
|
||||
return [];
|
||||
// if not 200 or 404 - throw a unique for each code exception
|
||||
case 403:
|
||||
throw P1nkl0bst3rForbidden();
|
||||
case 429:
|
||||
throw P1nkl0bst3rTooManyRequests();
|
||||
case 418:
|
||||
throw TetrioOskwareBridgeProblem();
|
||||
case 500:
|
||||
case 502:
|
||||
case 503:
|
||||
case 504:
|
||||
throw P1nkl0bst3rInternalProblem();
|
||||
default:
|
||||
developer.log("fetchCutoffs: Failed to fetch top Cutoffs", name: "services/tetrio_crud", error: response.statusCode);
|
||||
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
|
||||
}
|
||||
} on http.ClientException catch (e, s) { // If local http client fails
|
||||
developer.log("$e, $s");
|
||||
throw http.ClientException(e.message, e.uri); // just assuming, that our end user don't have acess to the internet
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves Tetra League history from p1nkl0bst3r api for a player with given [id]. Returns a list of states
|
||||
/// (state = instance of [TetrioPlayer] at some point of time). Can throw an exception if fails to retrieve data.
|
||||
Future<List<TetrioPlayer>> fetchAndsaveTLHistory(String id) async {
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:intl/intl.dart';
|
|||
import 'package:tetra_stats/gen/strings.g.dart';
|
||||
|
||||
final NumberFormat comparef = NumberFormat("+#,###.###;-#,###.###")..maximumFractionDigits = 3;
|
||||
final NumberFormat comparef2 = NumberFormat("+#,###.##;-#,###.##")..maximumFractionDigits = 2;
|
||||
final NumberFormat intf = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0);
|
||||
final NumberFormat f4 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 4);
|
||||
final NumberFormat f3 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 3);
|
||||
|
|
|
@ -182,12 +182,13 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
|||
teto.fetchTLStream(_searchFor),
|
||||
teto.fetchRecords(_searchFor),
|
||||
teto.fetchNews(_searchFor),
|
||||
prefs.getBool("showPositions") != true ? teto.fetchCutoffs() : Future.delayed(Duration.zero, ()=>[]),
|
||||
if (me.tlSeason1.gamesPlayed > 9) teto.fetchTopTR(_searchFor) // can retrieve this only if player has TR
|
||||
]);
|
||||
tlStream = requests[0] as TetraLeagueAlphaStream;
|
||||
records = requests[1] as Map<String, dynamic>;
|
||||
news = requests[2] as List<News>;
|
||||
topTR = requests.elementAtOrNull(3) as double?; // No TR - no Top TR
|
||||
topTR = requests.elementAtOrNull(4) as double?; // No TR - no Top TR
|
||||
|
||||
meAmongEveryone = teto.getCachedLeaderboardPositions(me.userId);
|
||||
if (prefs.getBool("showPositions") == true){
|
||||
|
@ -198,15 +199,17 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
|||
meAmongEveryone = await compute(everyone!.getLeaderboardPosition, me);
|
||||
if (meAmongEveryone != null) teto.cacheLeaderboardPositions(me.userId, meAmongEveryone!);
|
||||
}
|
||||
if (me.tlSeason1.gamesPlayed > 9) {
|
||||
thatRankCutoff = everyone!.cutoffs[me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank];
|
||||
thatRankGlickoCutoff = everyone!.cutoffsGlicko[me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank];
|
||||
nextRankCutoff = everyone!.cutoffs[ranks.elementAtOrNull(ranks.indexOf(me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank)+1)];
|
||||
nextRankGlickoCutoff = everyone!.cutoffsGlicko[ranks.elementAtOrNull(ranks.indexOf(me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank)+1)];
|
||||
}
|
||||
Map<String, double> cutoffs = prefs.getBool("showPositions") == true ? everyone!.cutoffs : requests[3][0];
|
||||
Map<String, double> cutoffsGlicko = prefs.getBool("showPositions") == true ? everyone!.cutoffsGlicko : requests[3][1];
|
||||
if (me.tlSeason1.gamesPlayed > 9) {
|
||||
thatRankCutoff = cutoffs[me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank];
|
||||
thatRankGlickoCutoff = cutoffsGlicko[me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank];
|
||||
nextRankCutoff = cutoffs[ranks.elementAtOrNull(ranks.indexOf(me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank)+1)];
|
||||
nextRankGlickoCutoff = cutoffsGlicko[ranks.elementAtOrNull(ranks.indexOf(me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank)+1)];
|
||||
nextRankCutoff = nextRankCutoff??25000;
|
||||
nextRankGlickoCutoff = nextRankGlickoCutoff??double.infinity;
|
||||
}
|
||||
}
|
||||
|
||||
if (everyone != null && me.tlSeason1.gamesPlayed > 9) rankAverages = everyone?.averages[me.tlSeason1.percentileRank]?[0];
|
||||
|
||||
|
|
|
@ -33,10 +33,49 @@ class TLProgress extends StatelessWidget{
|
|||
Widget build(BuildContext context) {
|
||||
final glickoForWin = rate(tlData.glicko!, tlData.rd!, 0.06, [[tlData.glicko!, tlData.rd!, 1]], {})[0]-tlData.glicko!;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: 48,
|
||||
child: Stack(
|
||||
alignment: AlignmentDirectional.bottomCenter,
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Positioned(left: 0,
|
||||
child: RichText(
|
||||
textAlign: TextAlign.left,
|
||||
text: TextSpan(
|
||||
style: TextStyle(color: Colors.white, fontFamily: "Eurostile Round", fontSize: 12),
|
||||
children: [
|
||||
if (tlData.prevAt > 0) TextSpan(text: "№ ${f0.format(tlData.prevAt)}"),
|
||||
if (tlData.prevAt > 0 && previousRankTRcutoff != null) TextSpan(text: "\n"),
|
||||
if (previousRankTRcutoff != null) TextSpan(text: "${intf.format(previousRankTRcutoff)} (${comparef2.format(previousRankTRcutoff!-tlData.rating)}) TR"),
|
||||
if ((tlData.prevAt > 0 || previousRankTRcutoff != null) && previousGlickoCutoff != null) TextSpan(text: "\n"),
|
||||
if (previousGlickoCutoff != null) TextSpan(text: "~${f2.format((tlData.glicko!-previousGlickoCutoff!)/glickoForWin)} defeats")
|
||||
]
|
||||
)
|
||||
),
|
||||
),
|
||||
Positioned(right: 0,
|
||||
child: RichText(
|
||||
textAlign: TextAlign.right,
|
||||
text: TextSpan(
|
||||
style: TextStyle(color: Colors.white, fontFamily: "Eurostile Round", fontSize: 12),
|
||||
children: [
|
||||
if (tlData.nextAt > 0) TextSpan(text: "№ ${f0.format(tlData.nextAt)}"),
|
||||
if (tlData.nextAt > 0 && nextRankTRcutoff != null) TextSpan(text: "\n"),
|
||||
if (nextRankTRcutoff != null) TextSpan(text: "${intf.format(nextRankTRcutoff)} (${comparef2.format(nextRankTRcutoff!-tlData.rating)}) TR"),
|
||||
if ((tlData.nextAt > 0 || nextRankTRcutoff != null) && nextRankGlickoCutoff != null) TextSpan(text: "\n"),
|
||||
if (nextRankGlickoCutoff != null) TextSpan(text: "~${f2.format((nextRankGlickoCutoff!-tlData.glicko!)/glickoForWin)} victories")
|
||||
]
|
||||
)
|
||||
),
|
||||
)
|
||||
],),
|
||||
),
|
||||
SfLinearGauge(
|
||||
minimum: 0,
|
||||
maximum: 1,
|
||||
|
@ -49,34 +88,22 @@ class TLProgress extends StatelessWidget{
|
|||
],
|
||||
markerPointers: [
|
||||
LinearShapePointer(value: (previousRankTRcutoff != null && nextRankTRcutoff != null) ? getBarTR(tlData.rating)! : getBarPosition(), position: LinearElementPosition.cross, shapeType: LinearShapePointerType.diamond, color: Colors.white, height: 20),
|
||||
LinearWidgetPointer(offset: 4, position: LinearElementPosition.outside, value: (previousRankTRcutoff != null && nextRankTRcutoff != null) ? getBarTR(tlData.rating)! : getBarPosition(), child: Text("№ ${NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0).format(tlData.standing)}"))
|
||||
if (tlData.standing != -1) LinearWidgetPointer(offset: 4, position: LinearElementPosition.outside, value: (previousRankTRcutoff != null && nextRankTRcutoff != null) ? getBarTR(tlData.rating)! : getBarPosition(), child: Text("№ ${NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0).format(tlData.standing)}", style: TextStyle(fontSize: 14),))
|
||||
],
|
||||
isMirrored: true,
|
||||
showTicks: true,
|
||||
axisLabelStyle: TextStyle(),
|
||||
onGenerateLabels: () => [
|
||||
LinearAxisLabel(text: "${tlData.prevAt > 0 ? "№ ${f0.format(tlData.prevAt)}" : ""}\n ${intf.format(previousRankTRcutoff)} TR", value: 0),
|
||||
LinearAxisLabel(text: "${tlData.nextAt > 0 ? "№ ${f0.format(tlData.nextAt)}" : ""}\n ${intf.format(nextRankTRcutoff)} TR", value: 1),
|
||||
],
|
||||
// onGenerateLabels: () => [
|
||||
// LinearAxisLabel(text: "${tlData.prevAt > 0 ? "№ ${f0.format(tlData.prevAt)}" : ""}\n ${intf.format(previousRankTRcutoff)} TR", value: 0),
|
||||
// LinearAxisLabel(text: "${tlData.nextAt > 0 ? "№ ${f0.format(tlData.nextAt)}" : ""}\n ${intf.format(nextRankTRcutoff)} TR", value: 1),
|
||||
// ],
|
||||
// labelFormatterCallback: (value) {
|
||||
// if (value == "0") return "${f0.format(previousRankPosition)}\n 26,700 TR";
|
||||
// else return f0.format(nextRankPosition);
|
||||
// },
|
||||
showLabels: true
|
||||
),
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: 20,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Positioned(child: Text("${f2.format(tlData.rating-previousRankTRcutoff!)} (${f2.format((tlData.glicko!-previousGlickoCutoff!)/glickoForWin)} losses)"), left: 0,),
|
||||
Positioned(child: Text("${f2.format(nextRankTRcutoff!-tlData.rating)} (${f2.format((nextRankGlickoCutoff!-tlData.glicko!)/glickoForWin)} wins)"), right: 0,)
|
||||
],),
|
||||
showLabels: false
|
||||
)
|
||||
|
||||
],
|
||||
)
|
||||
]
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -31,7 +31,7 @@ class TLThingy extends StatefulWidget {
|
|||
final double? nextRankCutoff;
|
||||
final double? nextRankCutoffGlicko;
|
||||
final double? nextRankTarget;
|
||||
const TLThingy({super.key, required this.tl, required this.userID, required this.states, this.showTitle = true, this.bot=false, this.guest=false, this.topTR, this.lbPositions, this.averages, this.nextRankCutoff = 25000, this.thatRankCutoff = 0, this.thatRankCutoffGlicko = 0, this.nextRankCutoffGlicko = double.infinity, this.nextRankTarget = 25000, this.thatRankTarget = 0});
|
||||
const TLThingy({super.key, required this.tl, required this.userID, required this.states, this.showTitle = true, this.bot=false, this.guest=false, this.topTR, this.lbPositions, this.averages, this.nextRankCutoff, this.thatRankCutoff, this.thatRankCutoffGlicko, this.nextRankCutoffGlicko, this.nextRankTarget, this.thatRankTarget});
|
||||
|
||||
@override
|
||||
State<TLThingy> createState() => _TLThingyState();
|
||||
|
@ -342,9 +342,9 @@ class _TLThingyState extends State<TLThingy> {
|
|||
if (oldTl?.estTr?.esttr != null) TextSpan(text: comparef.format(currentTl.estTr!.esttr - oldTl!.estTr!.esttr), style: TextStyle(
|
||||
color: oldTl!.estTr!.esttr > currentTl.estTr!.esttr ? Colors.redAccent : Colors.greenAccent
|
||||
),),
|
||||
if (oldTl?.estTr?.esttr != null || widget.lbPositions?.estTr != null) const TextSpan(text: " • "),
|
||||
if (oldTl?.estTr?.esttr != null) const TextSpan(text: " • "),
|
||||
if (widget.lbPositions?.estTr != null) TextSpan(text: widget.lbPositions!.estTr!.position >= 1000 ? "${t.top} ${f2.format(widget.lbPositions!.estTr!.percentage*100)}%" : "№${widget.lbPositions!.estTr!.position}", style: TextStyle(color: getColorOfRank(widget.lbPositions!.estTr!.position))),
|
||||
if (widget.lbPositions?.estTr != null) const TextSpan(text: " • "),
|
||||
if (widget.lbPositions?.estTr != null || oldTl?.estTr?.esttr != null) const TextSpan(text: " • "),
|
||||
TextSpan(text: "Glicko: ${f2.format(currentTl.estTr!.estglicko)}")
|
||||
]
|
||||
),
|
||||
|
|
Loading…
Reference in New Issue