diff --git a/lib/data_objects/tetrio.dart b/lib/data_objects/tetrio.dart index d67a38b..c3d22dc 100644 --- a/lib/data_objects/tetrio.dart +++ b/lib/data_objects/tetrio.dart @@ -38,6 +38,25 @@ const Map rankCutoffs = { "z": -1, "": 0.5 }; +const Map rankTargets = { + "x": 24008, + "u": 23038, + "ss": 21583, + "s+": 20128, + "s": 18673, + "s-": 16975, + "a+": 15035, + "a": 13095, + "a-": 11155, + "b+": 9215, + "b": 7275, + "b-": 5335, + "c+": 3880, + "c": 2425, + "c-": 1213, + "d+": 606, + "d": 0, +}; enum Stats { tr, glicko, @@ -1883,6 +1902,26 @@ class TetrioPlayersLeaderboard { 'z': getAverageOfRank("z") }; + Map get cutoffs => { + 'x': getAverageOfRank("x")[1]["toEnterTR"], + 'u': getAverageOfRank("u")[1]["toEnterTR"], + 'ss': getAverageOfRank("ss")[1]["toEnterTR"], + 's+': getAverageOfRank("s+")[1]["toEnterTR"], + 's': getAverageOfRank("s")[1]["toEnterTR"], + 's-': getAverageOfRank("s-")[1]["toEnterTR"], + 'a+': getAverageOfRank("a+")[1]["toEnterTR"], + 'a': getAverageOfRank("a")[1]["toEnterTR"], + 'a-': getAverageOfRank("a-")[1]["toEnterTR"], + 'b+': getAverageOfRank("b+")[1]["toEnterTR"], + 'b': getAverageOfRank("b")[1]["toEnterTR"], + 'b-': getAverageOfRank("b-")[1]["toEnterTR"], + 'c+': getAverageOfRank("c+")[1]["toEnterTR"], + 'c': getAverageOfRank("c")[1]["toEnterTR"], + 'c-': getAverageOfRank("c-")[1]["toEnterTR"], + 'd+': getAverageOfRank("d+")[1]["toEnterTR"], + 'd': getAverageOfRank("d")[1]["toEnterTR"] + }; + TetrioPlayersLeaderboard.fromJson(List json, String t, DateTime ts) { type = t; timestamp = ts; diff --git a/lib/services/tetrio_crud.dart b/lib/services/tetrio_crud.dart index a99c8c7..4090a38 100644 --- a/lib/services/tetrio_crud.dart +++ b/lib/services/tetrio_crud.dart @@ -728,6 +728,14 @@ class TetrioService extends DB { return matches; } + /// Gets and returns an amount of stored Tetra League mathes between [ourPlayerID] and [enemyPlayerID]. + Future getNumberOfTLMatchesBetweenPlayers(String ourPlayerID, String enemyPlayerID) async { + await ensureDbIsOpen(); + final db = getDatabaseOrThrow(); + final results = await db.rawQuery("SELECT COUNT(*) from tetrioAlphaLeagueMathces WHERE (player1id = $ourPlayerID AND player2id = $enemyPlayerID) OR (player1id = $enemyPlayerID AND player2id = $ourPlayerID)"); + return results.first.values.first as int; + } + /// Deletes match and stats of that match with given [matchID] from local DB. Throws an exception if fails. Future deleteTLMatch(String matchID) async { await ensureDbIsOpen(); diff --git a/lib/views/main_view.dart b/lib/views/main_view.dart index 1819f64..ea38a06 100644 --- a/lib/views/main_view.dart +++ b/lib/views/main_view.dart @@ -88,6 +88,7 @@ class _MainState extends State with TickerProviderStateMixin { final bodyGlobalKey = GlobalKey(); bool _showSearchBar = false; late TabController _tabController; + late TabController _wideScreenTabController; late bool fixedScroll; @override @@ -95,6 +96,7 @@ class _MainState extends State with TickerProviderStateMixin { initDB(); _scrollController = ScrollController(); _tabController = TabController(length: 6, vsync: this); + _wideScreenTabController = TabController(length: 4, vsync: this); // We need to show something if (widget.player != null){ // if we have user input, @@ -279,12 +281,12 @@ class _MainState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { final t = Translations.of(context); - bool bigScreen = MediaQuery.of(context).size.width > 768; + bool bigScreen = MediaQuery.of(context).size.width > 1024; return Scaffold( drawer: widget.player == null ? NavDrawer(changePlayer) : null, // Side menu hidden if player provided drawerEdgeDragWidth: MediaQuery.of(context).size.width * 0.2, // 20% of left side of the screen used of Drawer gesture appBar: AppBar( - title: _showSearchBar ? SearchBox(onSubmit: changePlayer, bigScreen: bigScreen) : Text(widget.title, style: const TextStyle(shadows: textShadow)), + title: _showSearchBar ? SearchBox(onSubmit: changePlayer, bigScreen: MediaQuery.of(context).size.width > 768) : Text(widget.title, style: const TextStyle(shadows: textShadow)), backgroundColor: Colors.black, actions: widget.player == null ? [ // search bar and PopupMenuButton hidden if player provided TODO: Subject to change _showSearchBar @@ -374,8 +376,9 @@ class _MainState extends State with TickerProviderStateMixin { return notification.depth == 0; }, child: NestedScrollView( - controller: _scrollController, physics: const AlwaysScrollableScrollPhysics(), + controller: _scrollController, + scrollBehavior: const MaterialScrollBehavior(), headerSliverBuilder: (context, value) { return [ SliverToBoxAdapter( @@ -386,10 +389,15 @@ class _MainState extends State with TickerProviderStateMixin { )), SliverToBoxAdapter( child: TabBar( - controller: _tabController, + controller: bigScreen ? _wideScreenTabController : _tabController, padding: const EdgeInsets.all(0.0), isScrollable: true, - tabs: [ + tabs: bigScreen ? [ + Tab(text: t.tetraLeague,), + Tab(text: t.history), + Tab(text: "${t.sprint} & ${t.blitz}"), + Tab(text: t.other), + ] : [ Tab(text: t.tetraLeague), Tab(text: t.tlRecords), Tab(text: t.history), @@ -402,8 +410,44 @@ class _MainState extends State with TickerProviderStateMixin { ]; }, body: TabBarView( - controller: _tabController, - children: [ + controller: bigScreen ? _wideScreenTabController : _tabController, + children: bigScreen ? [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: MediaQuery.of(context).size.width-450, + constraints: BoxConstraints(maxWidth: 1024), + child: TLThingy( + tl: snapshot.data![0].tlSeason1, + userID: snapshot.data![0].userId, + states: snapshot.data![2], + topTR: snapshot.data![7], + bot: snapshot.data![0].role == "bot", + guest: snapshot.data![0].role == "anon", + lbPositions: meAmongEveryone + ), + ), + SizedBox( + width: 450, + child: _TLRecords(userID: snapshot.data![0].userId, data: snapshot.data![3]) + ), + ],), + _History(states: snapshot.data![2], update: _justUpdate), + Row(children: [ + Container( + width: MediaQuery.of(context).size.width/2, + padding: EdgeInsets.only(right: 8), + child: _RecordThingy(record: snapshot.data![1]['sprint'], rank: snapshot.data![0].tlSeason1.percentileRank) + ), + Container( + width: MediaQuery.of(context).size.width/2, + padding: EdgeInsets.only(left: 8), + child: _RecordThingy(record: snapshot.data![1]['blitz'], rank: snapshot.data![0].tlSeason1.percentileRank) + ), + ],), + _OtherThingy(zen: snapshot.data![1]['zen'], bio: snapshot.data![0].bio, distinguishment: snapshot.data![0].distinguishment, newsletter: snapshot.data![6],) + ] : [ TLThingy( tl: snapshot.data![0].tlSeason1, userID: snapshot.data![0].userId, @@ -600,9 +644,10 @@ class _TLRecords extends StatelessWidget { @override Widget build(BuildContext context) { if (data.isEmpty) return Center(child: Text(t.noRecords, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28))); - bool bigScreen = MediaQuery.of(context).size.width > 768; + bool bigScreen = MediaQuery.of(context).size.width >= 768; return ListView.builder( physics: const AlwaysScrollableScrollPhysics(), + controller: ScrollController(), itemCount: data.length, itemBuilder: (BuildContext context, int index) { var accentColor = data[index].endContext.firstWhere((element) => element.userId == userID).success ? Colors.green : Colors.red; diff --git a/lib/widgets/tl_thingy.dart b/lib/widgets/tl_thingy.dart index 437253f..0fb9104 100644 --- a/lib/widgets/tl_thingy.dart +++ b/lib/widgets/tl_thingy.dart @@ -27,7 +27,11 @@ class TLThingy extends StatefulWidget { final double? topTR; final PlayerLeaderboardPosition? lbPositions; final TetraLeagueAlpha? averages; - 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}); + final double? thatRankCutoff; + final double? thatRankTarget; + final double? nextRankCutoff; + 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.nextRankTarget = 25000, this.thatRankTarget = 0}); @override State createState() => _TLThingyState(); @@ -55,7 +59,7 @@ class _TLThingyState extends State { NumberFormat fractionfEstTRAcc = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 3)..maximumIntegerDigits = 0; if (currentTl.gamesPlayed == 0) return Center(child: Text(widget.guest ? t.anonTL : widget.bot ? t.botTL : t.neverPlayedTL, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28), textAlign: TextAlign.center,)); return LayoutBuilder(builder: (context, constraints) { - bool bigScreen = constraints.maxWidth > 768; + bool bigScreen = constraints.maxWidth >= 768; return ListView.builder( physics: const ClampingScrollPhysics(), itemCount: 1, @@ -313,7 +317,9 @@ class _TLThingyState extends State { color: oldTl!.estTr!.esttr > currentTl.estTr!.esttr ? Colors.redAccent : Colors.greenAccent ),), if (oldTl?.estTr?.esttr != null && widget.lbPositions?.estTr != null) const TextSpan(text: " • "), - if (widget.lbPositions?.estTr != null) TextSpan(text: widget.lbPositions!.estTr!.position >= 1000 ? "Top ${f2.format(widget.lbPositions!.estTr!.percentage*100)}%" : "№${widget.lbPositions!.estTr!.position}") + if (widget.lbPositions?.estTr != null) TextSpan(text: widget.lbPositions!.estTr!.position >= 1000 ? "Top ${f2.format(widget.lbPositions!.estTr!.percentage*100)}%" : "№${widget.lbPositions!.estTr!.position}"), + if (widget.lbPositions?.estTr != null) const TextSpan(text: " • "), + TextSpan(text: "Glicko: ${f2.format(currentTl.estTr!.estglicko)}") ] ), ),