1.4.0?
This commit is contained in:
parent
14def01b57
commit
d0ead79068
|
@ -40,37 +40,37 @@ jobs:
|
||||||
tag: Auto-${{ github.run_number }}
|
tag: Auto-${{ github.run_number }}
|
||||||
body: Builded with GitHub Action workflow
|
body: Builded with GitHub Action workflow
|
||||||
token: ${{ secrets.TOKEN }}
|
token: ${{ secrets.TOKEN }}
|
||||||
build-and-release-linux:
|
# build-and-release-linux:
|
||||||
name: Build Linux App
|
# name: Build Linux App
|
||||||
runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
steps:
|
# steps:
|
||||||
- uses: actions/checkout@v2
|
# - uses: actions/checkout@v2
|
||||||
- uses: subosito/flutter-action@v1
|
# - uses: subosito/flutter-action@v1
|
||||||
- uses: ashutoshvarma/setup-ninja@master
|
# - uses: ashutoshvarma/setup-ninja@master
|
||||||
with:
|
# with:
|
||||||
channel: 'stable'
|
# channel: 'stable'
|
||||||
flutter-version: '3.16.5'
|
# flutter-version: '3.16.5'
|
||||||
- name: Install project dependencies
|
# - name: Install project dependencies
|
||||||
run: flutter pub get
|
# run: flutter pub get
|
||||||
- name: Build artifacts
|
# - name: Build artifacts
|
||||||
run: flutter build linux --release
|
# run: flutter build linux --release
|
||||||
- name: Archive Release
|
# - name: Archive Release
|
||||||
uses: thedoctor0/zip-release@master
|
# uses: thedoctor0/zip-release@master
|
||||||
with:
|
# with:
|
||||||
type: 'zip'
|
# type: 'zip'
|
||||||
filename: TetraStats-${{github.ref_name}}-windows.zip
|
# filename: TetraStats-${{github.ref_name}}-windows.zip
|
||||||
directory: build/linux/x64/runner/Release/bundle
|
# directory: build/linux/x64/runner/Release/bundle
|
||||||
- name: Push to Releases
|
# - name: Push to Releases
|
||||||
uses: ncipollo/release-action@v1
|
# uses: ncipollo/release-action@v1
|
||||||
with:
|
# with:
|
||||||
prerelease: true
|
# prerelease: true
|
||||||
allowUpdates: true
|
# allowUpdates: true
|
||||||
replacesArtifacts: false
|
# replacesArtifacts: false
|
||||||
discussionCategory: autobuilded-releases
|
# discussionCategory: autobuilded-releases
|
||||||
artifacts: "build/linux/x64/runner/Release/bundle/TetraStats-${{github.ref_name}}-linux.zip"
|
# artifacts: "build/linux/x64/runner/Release/bundle/TetraStats-${{github.ref_name}}-linux.zip"
|
||||||
tag: Auto-${{ github.run_number }}
|
# tag: Auto-${{ github.run_number }}
|
||||||
body: Builded with GitHub Action workflow
|
# body: Builded with GitHub Action workflow
|
||||||
token: ${{ secrets.TOKEN }}
|
# token: ${{ secrets.TOKEN }}
|
||||||
build-and-release-android:
|
build-and-release-android:
|
||||||
name: Build Android App
|
name: Build Android App
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
|
@ -661,7 +661,7 @@ class NerdStats {
|
||||||
dsp = ((_vs / 100) - (_apm / 60)) / _pps;
|
dsp = ((_vs / 100) - (_apm / 60)) / _pps;
|
||||||
appdsp = app + dsp;
|
appdsp = app + dsp;
|
||||||
cheese = (dsp * 150) + ((vsapm - 2) * 50) + (0.6 - app) * 125;
|
cheese = (dsp * 150) + ((vsapm - 2) * 50) + (0.6 - app) * 125;
|
||||||
gbe = ((app * dss) / _pps) * 2;
|
gbe = app * dsp * 2;
|
||||||
nyaapp = app - 5 * tan(radians((cheese / -30) + 1));
|
nyaapp = app - 5 * tan(radians((cheese / -30) + 1));
|
||||||
area = _apm * 1 + _pps * 45 + _vs * 0.444 + app * 185 + dss * 175 + dsp * 450 + gbe * 315;
|
area = _apm * 1 + _pps * 45 + _vs * 0.444 + app * 185 + dss * 175 + dsp * 450 + gbe * 315;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
/// To regenerate, run: `dart run slang`
|
/// To regenerate, run: `dart run slang`
|
||||||
///
|
///
|
||||||
/// Locales: 2
|
/// Locales: 2
|
||||||
/// Strings: 994 (497 per locale)
|
/// Strings: 1004 (502 per locale)
|
||||||
///
|
///
|
||||||
/// Built on 2024-01-05 at 16:51 UTC
|
/// Built on 2024-01-22 at 17:10 UTC
|
||||||
|
|
||||||
// coverage:ignore-file
|
// coverage:ignore-file
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
|
@ -182,6 +182,8 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
|
||||||
String get anonRecord => 'Guests are not allowed to set records';
|
String get anonRecord => 'Guests are not allowed to set records';
|
||||||
String get notEnoughData => 'Not enough data';
|
String get notEnoughData => 'Not enough data';
|
||||||
String get noHistorySaved => 'No history saved';
|
String get noHistorySaved => 'No history saved';
|
||||||
|
String get pseudoTooltipHeaderInit => 'Hover over point';
|
||||||
|
String get pseudoTooltipFooterInit => 'to see detailed data';
|
||||||
String obtainDate({required Object date}) => 'Obtained ${date}';
|
String obtainDate({required Object date}) => 'Obtained ${date}';
|
||||||
String fetchDate({required Object date}) => 'Fetched ${date}';
|
String fetchDate({required Object date}) => 'Fetched ${date}';
|
||||||
String get exactGametime => 'Exact gametime';
|
String get exactGametime => 'Exact gametime';
|
||||||
|
@ -250,6 +252,9 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
|
||||||
String get match => 'Match';
|
String get match => 'Match';
|
||||||
String roundNumber({required Object n}) => 'Round ${n}';
|
String roundNumber({required Object n}) => 'Round ${n}';
|
||||||
String get statsFor => 'Stats for';
|
String get statsFor => 'Stats for';
|
||||||
|
String get matchLength => 'Match Length';
|
||||||
|
String get roundLength => 'Round Length';
|
||||||
|
String get winner => 'Winner';
|
||||||
String get registred => 'Registred';
|
String get registred => 'Registred';
|
||||||
String get playedTL => 'Played Tetra League';
|
String get playedTL => 'Played Tetra League';
|
||||||
String get winChance => 'Win Chance';
|
String get winChance => 'Win Chance';
|
||||||
|
@ -763,6 +768,8 @@ class _StringsRu implements Translations {
|
||||||
@override String get anonRecord => 'Гостям нельзя ставить рекорды';
|
@override String get anonRecord => 'Гостям нельзя ставить рекорды';
|
||||||
@override String get notEnoughData => 'Недостаточно данных';
|
@override String get notEnoughData => 'Недостаточно данных';
|
||||||
@override String get noHistorySaved => 'Нет сохранённой истории';
|
@override String get noHistorySaved => 'Нет сохранённой истории';
|
||||||
|
@override String get pseudoTooltipHeaderInit => 'Наведите курсор на точку';
|
||||||
|
@override String get pseudoTooltipFooterInit => 'чтобы узнать подробности';
|
||||||
@override String obtainDate({required Object date}) => 'Получено ${date}';
|
@override String obtainDate({required Object date}) => 'Получено ${date}';
|
||||||
@override String fetchDate({required Object date}) => 'На момент ${date}';
|
@override String fetchDate({required Object date}) => 'На момент ${date}';
|
||||||
@override String get exactGametime => 'Время, проведённое в игре';
|
@override String get exactGametime => 'Время, проведённое в игре';
|
||||||
|
@ -831,6 +838,9 @@ class _StringsRu implements Translations {
|
||||||
@override String get match => 'Матч';
|
@override String get match => 'Матч';
|
||||||
@override String roundNumber({required Object n}) => 'Раунд ${n}';
|
@override String roundNumber({required Object n}) => 'Раунд ${n}';
|
||||||
@override String get statsFor => 'Статистика за';
|
@override String get statsFor => 'Статистика за';
|
||||||
|
@override String get matchLength => 'Продолжительность матча';
|
||||||
|
@override String get roundLength => 'Продолжительность раунда';
|
||||||
|
@override String get winner => 'Победитель';
|
||||||
@override String get registred => 'Зарегистрирован';
|
@override String get registred => 'Зарегистрирован';
|
||||||
@override String get playedTL => 'Играл в Тетра Лигу';
|
@override String get playedTL => 'Играл в Тетра Лигу';
|
||||||
@override String get winChance => 'Шансы на победу';
|
@override String get winChance => 'Шансы на победу';
|
||||||
|
@ -1336,6 +1346,8 @@ extension on Translations {
|
||||||
case 'anonRecord': return 'Guests are not allowed to set records';
|
case 'anonRecord': return 'Guests are not allowed to set records';
|
||||||
case 'notEnoughData': return 'Not enough data';
|
case 'notEnoughData': return 'Not enough data';
|
||||||
case 'noHistorySaved': return 'No history saved';
|
case 'noHistorySaved': return 'No history saved';
|
||||||
|
case 'pseudoTooltipHeaderInit': return 'Hover over point';
|
||||||
|
case 'pseudoTooltipFooterInit': return 'to see detailed data';
|
||||||
case 'obtainDate': return ({required Object date}) => 'Obtained ${date}';
|
case 'obtainDate': return ({required Object date}) => 'Obtained ${date}';
|
||||||
case 'fetchDate': return ({required Object date}) => 'Fetched ${date}';
|
case 'fetchDate': return ({required Object date}) => 'Fetched ${date}';
|
||||||
case 'exactGametime': return 'Exact gametime';
|
case 'exactGametime': return 'Exact gametime';
|
||||||
|
@ -1404,6 +1416,9 @@ extension on Translations {
|
||||||
case 'match': return 'Match';
|
case 'match': return 'Match';
|
||||||
case 'roundNumber': return ({required Object n}) => 'Round ${n}';
|
case 'roundNumber': return ({required Object n}) => 'Round ${n}';
|
||||||
case 'statsFor': return 'Stats for';
|
case 'statsFor': return 'Stats for';
|
||||||
|
case 'matchLength': return 'Match Length';
|
||||||
|
case 'roundLength': return 'Round Length';
|
||||||
|
case 'winner': return 'Winner';
|
||||||
case 'registred': return 'Registred';
|
case 'registred': return 'Registred';
|
||||||
case 'playedTL': return 'Played Tetra League';
|
case 'playedTL': return 'Played Tetra League';
|
||||||
case 'winChance': return 'Win Chance';
|
case 'winChance': return 'Win Chance';
|
||||||
|
@ -1843,6 +1858,8 @@ extension on _StringsRu {
|
||||||
case 'anonRecord': return 'Гостям нельзя ставить рекорды';
|
case 'anonRecord': return 'Гостям нельзя ставить рекорды';
|
||||||
case 'notEnoughData': return 'Недостаточно данных';
|
case 'notEnoughData': return 'Недостаточно данных';
|
||||||
case 'noHistorySaved': return 'Нет сохранённой истории';
|
case 'noHistorySaved': return 'Нет сохранённой истории';
|
||||||
|
case 'pseudoTooltipHeaderInit': return 'Наведите курсор на точку';
|
||||||
|
case 'pseudoTooltipFooterInit': return 'чтобы узнать подробности';
|
||||||
case 'obtainDate': return ({required Object date}) => 'Получено ${date}';
|
case 'obtainDate': return ({required Object date}) => 'Получено ${date}';
|
||||||
case 'fetchDate': return ({required Object date}) => 'На момент ${date}';
|
case 'fetchDate': return ({required Object date}) => 'На момент ${date}';
|
||||||
case 'exactGametime': return 'Время, проведённое в игре';
|
case 'exactGametime': return 'Время, проведённое в игре';
|
||||||
|
@ -1911,6 +1928,9 @@ extension on _StringsRu {
|
||||||
case 'match': return 'Матч';
|
case 'match': return 'Матч';
|
||||||
case 'roundNumber': return ({required Object n}) => 'Раунд ${n}';
|
case 'roundNumber': return ({required Object n}) => 'Раунд ${n}';
|
||||||
case 'statsFor': return 'Статистика за';
|
case 'statsFor': return 'Статистика за';
|
||||||
|
case 'matchLength': return 'Продолжительность матча';
|
||||||
|
case 'roundLength': return 'Продолжительность раунда';
|
||||||
|
case 'winner': return 'Победитель';
|
||||||
case 'registred': return 'Зарегистрирован';
|
case 'registred': return 'Зарегистрирован';
|
||||||
case 'playedTL': return 'Играл в Тетра Лигу';
|
case 'playedTL': return 'Играл в Тетра Лигу';
|
||||||
case 'winChance': return 'Шансы на победу';
|
case 'winChance': return 'Шансы на победу';
|
||||||
|
|
|
@ -57,6 +57,7 @@ const String createTetrioTLReplayStats = '''
|
||||||
CREATE TABLE IF NOT EXISTS "tetrioTLReplayStats" (
|
CREATE TABLE IF NOT EXISTS "tetrioTLReplayStats" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"data" TEXT NOT NULL,
|
"data" TEXT NOT NULL,
|
||||||
|
"freyhoe" TEXT NOT NULL,
|
||||||
PRIMARY KEY("id")
|
PRIMARY KEY("id")
|
||||||
)
|
)
|
||||||
''';
|
''';
|
||||||
|
@ -70,8 +71,8 @@ class TetrioService extends DB {
|
||||||
final Map<String, List<News>> _newsCache = {};
|
final Map<String, List<News>> _newsCache = {};
|
||||||
final Map<String, Map<String, double?>> _topTRcache = {};
|
final Map<String, Map<String, double?>> _topTRcache = {};
|
||||||
final Map<String, TetraLeagueAlphaStream> _tlStreamsCache = {}; // i'm trying to respect oskware api It should look something like {"cached_until": TetrioPlayer}
|
final Map<String, TetraLeagueAlphaStream> _tlStreamsCache = {}; // i'm trying to respect oskware api It should look something like {"cached_until": TetrioPlayer}
|
||||||
//final client = UserAgentClient("Tetra Stats v${packageInfo.version} (dm @dan63047 if someone abuse that software)", http.Client());
|
final client = UserAgentClient("Tetra Stats v${packageInfo.version} (dm @dan63047 if someone abuse that software)", http.Client());
|
||||||
final client = UserAgentClient("Kagari-chan loves osk (Tetra Stats dev build)", http.Client());
|
//final client = UserAgentClient("Kagari-chan loves osk (Tetra Stats dev build)", http.Client());
|
||||||
static final TetrioService _shared = TetrioService._sharedInstance();
|
static final TetrioService _shared = TetrioService._sharedInstance();
|
||||||
factory TetrioService() => _shared;
|
factory TetrioService() => _shared;
|
||||||
late final StreamController<Map<String, List<TetrioPlayer>>> _tetrioStreamController;
|
late final StreamController<Map<String, List<TetrioPlayer>>> _tetrioStreamController;
|
||||||
|
@ -129,16 +130,28 @@ class TetrioService extends DB {
|
||||||
|
|
||||||
Future<List<dynamic>> szyGetReplay(String replayID) async {
|
Future<List<dynamic>> szyGetReplay(String replayID) async {
|
||||||
try{
|
try{
|
||||||
|
// read from cache
|
||||||
var cached = _replaysCache.entries.firstWhere((element) => element.key == replayID);
|
var cached = _replaysCache.entries.firstWhere((element) => element.key == replayID);
|
||||||
return cached.value;
|
return cached.value;
|
||||||
}catch (e){
|
}catch (e){
|
||||||
// actually going to obtain
|
// actually going to obtain
|
||||||
}
|
}
|
||||||
Uri url = Uri.https('inoue.szy.lol', '/api/replay/$replayID');
|
|
||||||
var downloadPath = await getDownloadsDirectory();
|
Uri url;
|
||||||
downloadPath ??= Platform.isAndroid ? Directory("/storage/emulated/0/Download") : await getApplicationDocumentsDirectory();
|
if (kIsWeb) {
|
||||||
var replayFile = File("${downloadPath.path}/$replayID.ttrm");
|
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "tetrioReplay", "replayid": replayID});
|
||||||
if (replayFile.existsSync()) return [replayFile.readAsStringSync(), replayFile.readAsBytesSync()];
|
} else {
|
||||||
|
url = Uri.https('inoue.szy.lol', '/api/replay/$replayID');
|
||||||
|
}
|
||||||
|
|
||||||
|
// trying to obtain replay from download directory first
|
||||||
|
if (!kIsWeb){ // can't obtain download directory on web
|
||||||
|
var downloadPath = await getDownloadsDirectory();
|
||||||
|
downloadPath ??= Platform.isAndroid ? Directory("/storage/emulated/0/Download") : await getApplicationDocumentsDirectory();
|
||||||
|
var replayFile = File("${downloadPath.path}/$replayID.ttrm");
|
||||||
|
if (replayFile.existsSync()) return [replayFile.readAsStringSync(), replayFile.readAsBytesSync()];
|
||||||
|
}
|
||||||
|
|
||||||
try{
|
try{
|
||||||
final response = await client.get(url);
|
final response = await client.get(url);
|
||||||
|
|
||||||
|
@ -533,10 +546,12 @@ class TetrioService extends DB {
|
||||||
Future<void> deleteTLMatch(String matchID) async {
|
Future<void> deleteTLMatch(String matchID) async {
|
||||||
await ensureDbIsOpen();
|
await ensureDbIsOpen();
|
||||||
final db = getDatabaseOrThrow();
|
final db = getDatabaseOrThrow();
|
||||||
|
final rID = (await db.query(tetraLeagueMatchesTable, where: '$idCol = ?', whereArgs: [matchID])).first[replayID];
|
||||||
final results = await db.delete(tetraLeagueMatchesTable, where: '$idCol = ?', whereArgs: [matchID]);
|
final results = await db.delete(tetraLeagueMatchesTable, where: '$idCol = ?', whereArgs: [matchID]);
|
||||||
if (results != 1) {
|
if (results != 1) {
|
||||||
throw CouldNotDeleteMatch();
|
throw CouldNotDeleteMatch();
|
||||||
}
|
}
|
||||||
|
await db.delete(tetrioTLReplayStatsTable, where: '$idCol = ?', whereArgs: [rID]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> fetchRecords(String userID) async {
|
Future<Map<String, dynamic>> fetchRecords(String userID) async {
|
||||||
|
|
|
@ -591,7 +591,7 @@ class _History extends StatelessWidget{
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
if(chartsData[_chartsIndex].value!.length > 1) _HistoryChartThigy(data: chartsData[_chartsIndex].value!, title: "ss", yAxisTitle: _historyShortTitles[_chartsIndex], bigScreen: bigScreen, leftSpace: bigScreen? 80 : 45, yFormat: bigScreen? _f2 : NumberFormat.compact(),)
|
if(chartsData[_chartsIndex].value!.length > 1) _HistoryChartThigy(data: chartsData[_chartsIndex].value!, yAxisTitle: _historyShortTitles[_chartsIndex], bigScreen: bigScreen, leftSpace: bigScreen? 80 : 45, yFormat: bigScreen? _f2 : NumberFormat.compact(),)
|
||||||
else Center(child: Text(t.notEnoughData, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28)))
|
else Center(child: Text(t.notEnoughData, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28)))
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -601,49 +601,43 @@ class _History extends StatelessWidget{
|
||||||
|
|
||||||
class _HistoryChartThigy extends StatefulWidget{
|
class _HistoryChartThigy extends StatefulWidget{
|
||||||
final List<FlSpot> data;
|
final List<FlSpot> data;
|
||||||
final String title;
|
|
||||||
final String yAxisTitle;
|
final String yAxisTitle;
|
||||||
final bool bigScreen;
|
final bool bigScreen;
|
||||||
final double leftSpace;
|
final double leftSpace;
|
||||||
final NumberFormat yFormat;
|
final NumberFormat yFormat;
|
||||||
|
|
||||||
const _HistoryChartThigy({required this.data, required this.title, required this.yAxisTitle, required this.bigScreen, required this.leftSpace, required this.yFormat});
|
const _HistoryChartThigy({required this.data, required this.yAxisTitle, required this.bigScreen, required this.leftSpace, required this.yFormat});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_HistoryChartThigy> createState() => _HistoryChartThigyState();
|
State<_HistoryChartThigy> createState() => _HistoryChartThigyState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HistoryChartThigyState extends State<_HistoryChartThigy> {
|
class _HistoryChartThigyState extends State<_HistoryChartThigy> {
|
||||||
|
late String previousAxisTitle;
|
||||||
late double minX;
|
late double minX;
|
||||||
late double maxX;
|
late double maxX;
|
||||||
late double minY;
|
late double minY;
|
||||||
late double actualMinY;
|
late double actualMinY;
|
||||||
late double maxY;
|
late double maxY;
|
||||||
late double actualMaxY;
|
late double actualMaxY;
|
||||||
|
late double xScale;
|
||||||
|
late double yScale;
|
||||||
|
String headerTooltip = t.pseudoTooltipHeaderInit;
|
||||||
|
String footerTooltip = t.pseudoTooltipFooterInit;
|
||||||
|
int hoveredPointId = -1;
|
||||||
|
double scaleFactor = 5e2;
|
||||||
|
double dragFactor = 7e2;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState(){
|
void initState(){
|
||||||
super.initState();
|
super.initState();
|
||||||
minX = widget.data.first.x;
|
minX = widget.data.first.x;
|
||||||
maxX = widget.data.last.x;
|
maxX = widget.data.last.x;
|
||||||
minY = widget.data.reduce((value, element){
|
setMinMaxY();
|
||||||
num n = min(value.y, element.y);
|
previousAxisTitle = widget.yAxisTitle;
|
||||||
if (value.y == n) {
|
|
||||||
return value;
|
|
||||||
} else {
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
}).y;
|
|
||||||
maxY = widget.data.reduce((value, element){
|
|
||||||
num n = max(value.y, element.y);
|
|
||||||
if (value.y == n) {
|
|
||||||
return value;
|
|
||||||
} else {
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
}).y;
|
|
||||||
actualMaxY = maxY;
|
actualMaxY = maxY;
|
||||||
actualMinY = minY;
|
actualMinY = minY;
|
||||||
|
recalculateScales();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -651,20 +645,93 @@ class _HistoryChartThigyState extends State<_HistoryChartThigy> {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
actualMinY = 0;
|
actualMinY = 0;
|
||||||
minY = 0;
|
minY = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setMinMaxY(){
|
||||||
|
actualMinY = widget.data.reduce((value, element){
|
||||||
|
num n = min(value.y, element.y);
|
||||||
|
if (value.y == n) {
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}).y;
|
||||||
|
actualMaxY = widget.data.reduce((value, element){
|
||||||
|
num n = max(value.y, element.y);
|
||||||
|
if (value.y == n) {
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}).y;
|
||||||
|
minY = actualMinY;
|
||||||
|
maxY = actualMaxY;
|
||||||
|
}
|
||||||
|
|
||||||
|
void recalculateScales(){
|
||||||
|
xScale = maxX - minX;
|
||||||
|
yScale = maxY - minY;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dragHandler(DragUpdateDetails dragUpdDet){
|
||||||
|
setState(() {
|
||||||
|
minX -= (xScale / dragFactor) * dragUpdDet.delta.dx;
|
||||||
|
maxX -= (xScale / dragFactor) * dragUpdDet.delta.dx;
|
||||||
|
minY += (yScale / dragFactor) * dragUpdDet.delta.dy;
|
||||||
|
maxY += (yScale / dragFactor) * dragUpdDet.delta.dy;
|
||||||
|
|
||||||
|
if (minX < widget.data.first.x) {
|
||||||
|
minX = widget.data.first.x;
|
||||||
|
maxX = widget.data.first.x + xScale;
|
||||||
|
}
|
||||||
|
if (maxX > widget.data.last.x) {
|
||||||
|
maxX = widget.data.last.x;
|
||||||
|
minX = maxX - xScale;
|
||||||
|
}
|
||||||
|
if(minY < actualMinY){
|
||||||
|
minY = actualMinY;
|
||||||
|
maxY = actualMinY + yScale;
|
||||||
|
}
|
||||||
|
if(maxY > actualMaxY){
|
||||||
|
maxY = actualMaxY;
|
||||||
|
minY = actualMaxY - yScale;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void scaleHandler(ScaleUpdateDetails details, GlobalKey<State<StatefulWidget>> graphKey, double graphStartX, double graphEndX){
|
||||||
|
RenderBox graphBox = graphKey.currentContext?.findRenderObject() as RenderBox;
|
||||||
|
Offset graphPosition = graphBox.localToGlobal(Offset.zero);
|
||||||
|
double scrollPosRelativeX = (details.focalPoint.dx - graphStartX) / (graphEndX - graphStartX);
|
||||||
|
double scrollPosRelativeY = (details.focalPoint.dy - graphPosition.dy) / (graphBox.size.height - 30); // size - bottom titles height
|
||||||
|
double newMinX, newMaxX, newMinY, newMaxY;
|
||||||
|
newMinX = minX - (xScale / scaleFactor) * (details.horizontalScale-1) * scrollPosRelativeX;
|
||||||
|
newMaxX = maxX + (xScale / scaleFactor) * (details.horizontalScale-1) * (1-scrollPosRelativeX);
|
||||||
|
newMinY = minY - (yScale / scaleFactor) * (details.horizontalScale-1) * (1-scrollPosRelativeY);
|
||||||
|
newMaxY = maxY + (yScale / scaleFactor) * (details.horizontalScale-1) * scrollPosRelativeY;
|
||||||
|
if ((newMaxX - newMinX).isNegative) return;
|
||||||
|
if ((newMaxY - newMinY).isNegative) return;
|
||||||
|
setState(() {
|
||||||
|
minX = max(newMinX, widget.data.first.x);
|
||||||
|
maxX = min(newMaxX, widget.data.last.x);
|
||||||
|
minY = max(newMinY, actualMinY);
|
||||||
|
maxY = min(newMaxY, actualMaxY);
|
||||||
|
recalculateScales();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
GlobalKey graphKey = GlobalKey();
|
GlobalKey graphKey = GlobalKey();
|
||||||
double xScale = maxX - minX;
|
|
||||||
double yScale = maxY - minY;
|
|
||||||
double scaleFactor = 5e2;
|
|
||||||
double dragFactor = 7e2;
|
|
||||||
double xInterval = widget.bigScreen ? max(1, xScale / 6) : max(1, xScale / 3);
|
double xInterval = widget.bigScreen ? max(1, xScale / 6) : max(1, xScale / 3);
|
||||||
EdgeInsets padding = widget.bigScreen ? const EdgeInsets.fromLTRB(40, 30, 40, 30) : const EdgeInsets.fromLTRB(0, 40, 16, 48);
|
EdgeInsets padding = widget.bigScreen ? const EdgeInsets.fromLTRB(40, 30, 40, 30) : const EdgeInsets.fromLTRB(0, 40, 16, 48);
|
||||||
double graphStartX = padding.left+widget.leftSpace;
|
double graphStartX = padding.left+widget.leftSpace;
|
||||||
double graphEndX = MediaQuery.sizeOf(context).width - padding.right;
|
double graphEndX = MediaQuery.sizeOf(context).width - padding.right;
|
||||||
|
if (previousAxisTitle != widget.yAxisTitle) {
|
||||||
|
setMinMaxY();
|
||||||
|
recalculateScales();
|
||||||
|
previousAxisTitle = widget.yAxisTitle;
|
||||||
|
}
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: MediaQuery.of(context).size.width,
|
width: MediaQuery.of(context).size.width,
|
||||||
height: MediaQuery.of(context).size.height - 104,
|
height: MediaQuery.of(context).size.height - 104,
|
||||||
|
@ -688,73 +755,90 @@ class _HistoryChartThigyState extends State<_HistoryChartThigy> {
|
||||||
maxX = min(newMaxX, widget.data.last.x);
|
maxX = min(newMaxX, widget.data.last.x);
|
||||||
minY = max(newMinY, actualMinY);
|
minY = max(newMinY, actualMinY);
|
||||||
maxY = min(newMaxY, actualMaxY);
|
maxY = min(newMaxY, actualMaxY);
|
||||||
_scrollController.jumpTo(_scrollController.position.maxScrollExtent - signal.scrollDelta.dy);
|
recalculateScales();
|
||||||
|
_scrollController.jumpTo(_scrollController.position.maxScrollExtent - signal.scrollDelta.dy); // TODO: find a better way to stop scrolling in NestedScrollView
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child:
|
child:
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.translucent,
|
||||||
onDoubleTap: () {
|
onDoubleTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
minX = widget.data.first.x;
|
minX = widget.data.first.x;
|
||||||
maxX = widget.data.last.x;
|
maxX = widget.data.last.x;
|
||||||
minY = actualMinY;
|
minY = actualMinY;
|
||||||
maxY = actualMaxY;
|
maxY = actualMaxY;
|
||||||
|
recalculateScales();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onPanUpdate: (dragUpdDet) {
|
// TODO: onScaleUpdate:(details) => scaleHandler(details, graphKey, graphStartX, graphEndX),
|
||||||
print(dragUpdDet);
|
// TODO: Figure out wtf is going on with gestures
|
||||||
|
// TODO: Somehow highlight touched spot (handleBuiltInTouches breaks getTooltipItems and getTouchedSpotIndicator)
|
||||||
setState(() {
|
child: Padding( padding: padding,
|
||||||
minX -= (xScale / dragFactor) * dragUpdDet.delta.dx;
|
child: Stack(
|
||||||
maxX -= (xScale / dragFactor) * dragUpdDet.delta.dx;
|
children: [
|
||||||
minY += (yScale / dragFactor) * dragUpdDet.delta.dy;
|
LineChart(
|
||||||
maxY += (yScale / dragFactor) * dragUpdDet.delta.dy;
|
key: graphKey,
|
||||||
|
LineChartData(
|
||||||
if (minX < widget.data.first.x) {
|
lineBarsData: [LineChartBarData(spots: widget.data)],
|
||||||
minX = widget.data.first.x;
|
clipData: const FlClipData.all(),
|
||||||
maxX = widget.data.first.x + xScale;
|
borderData: FlBorderData(show: false),
|
||||||
}
|
gridData: FlGridData(verticalInterval: xInterval),
|
||||||
if (maxX > widget.data.last.x) {
|
minX: minX,
|
||||||
maxX = widget.data.last.x;
|
maxX: maxX,
|
||||||
minX = maxX - xScale;
|
minY: minY,
|
||||||
}
|
maxY: maxY,
|
||||||
});
|
titlesData: FlTitlesData(topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||||
},
|
rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||||
child: AbsorbPointer(
|
bottomTitles: AxisTitles(sideTitles: SideTitles(interval: xInterval, showTitles: true, reservedSize: 30, getTitlesWidget: (double value, TitleMeta meta){
|
||||||
child: Padding( padding: padding,
|
return value != meta.min && value != meta.max ? SideTitleWidget(
|
||||||
child: LineChart(
|
axisSide: meta.axisSide,
|
||||||
key: graphKey,
|
child: Text(DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).format(DateTime.fromMillisecondsSinceEpoch(value.floor()))),
|
||||||
LineChartData(
|
) : Container();
|
||||||
lineBarsData: [LineChartBarData(spots: widget.data)],
|
})),
|
||||||
clipData: const FlClipData.all(),
|
leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: true, reservedSize: widget.leftSpace, getTitlesWidget: (double value, TitleMeta meta){
|
||||||
borderData: FlBorderData(show: false),
|
return value != meta.min && value != meta.max ? SideTitleWidget(
|
||||||
gridData: FlGridData(verticalInterval: xInterval),
|
axisSide: meta.axisSide,
|
||||||
minX: minX,
|
child: Text(widget.yFormat.format(value)),
|
||||||
maxX: maxX,
|
) : Container();
|
||||||
minY: minY,
|
}))),
|
||||||
maxY: maxY,
|
lineTouchData: LineTouchData(
|
||||||
titlesData: FlTitlesData(topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
handleBuiltInTouches: false,
|
||||||
rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
touchCallback:(touchEvent, touchResponse) {
|
||||||
bottomTitles: AxisTitles(sideTitles: SideTitles(interval: xInterval, showTitles: true, reservedSize: 30, getTitlesWidget: (double value, TitleMeta meta){
|
if (touchEvent is FlPanUpdateEvent){
|
||||||
return value != meta.min && value != meta.max ? SideTitleWidget(
|
dragHandler(touchEvent.details);
|
||||||
axisSide: meta.axisSide,
|
return;
|
||||||
child: Text(DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).format(DateTime.fromMillisecondsSinceEpoch(value.floor()))),
|
}
|
||||||
) : Container();
|
if (touchEvent is FlPointerHoverEvent){
|
||||||
})),
|
setState(() {
|
||||||
leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: true, reservedSize: widget.leftSpace, getTitlesWidget: (double value, TitleMeta meta){
|
if (touchResponse?.lineBarSpots?.first == null) {
|
||||||
return value != meta.min && value != meta.max ? SideTitleWidget(
|
hoveredPointId = -1;
|
||||||
axisSide: meta.axisSide,
|
} else {
|
||||||
child: Text(widget.yFormat.format(value)),
|
hoveredPointId = touchResponse!.lineBarSpots!.first.spotIndex;
|
||||||
) : Container();
|
headerTooltip = "${_f4.format(touchResponse.lineBarSpots!.first.y)} ${widget.yAxisTitle}";
|
||||||
}))),
|
footerTooltip = _dateFormat.format(DateTime.fromMillisecondsSinceEpoch(touchResponse.lineBarSpots!.first.x.floor()));
|
||||||
lineTouchData: LineTouchData(touchTooltipData: LineTouchTooltipData( fitInsideHorizontally: true, fitInsideVertically: true, getTooltipItems: (touchedSpots) {
|
}
|
||||||
return [for (var v in touchedSpots) LineTooltipItem("${_f4.format(v.y)} ${widget.yAxisTitle} \n", const TextStyle(), children: [TextSpan(text: _dateFormat.format(DateTime.fromMillisecondsSinceEpoch(v.x.floor())))])];
|
});
|
||||||
},))
|
}
|
||||||
|
if (touchEvent is FlPointerExitEvent){
|
||||||
|
setState(() {hoveredPointId = -1;});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(left: widget.leftSpace),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
AnimatedDefaultTextStyle(style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 24, color: Color.fromARGB(hoveredPointId == -1 ? 100 : 255, 255, 255, 255), shadows: hoveredPointId != -1 ? textShadow : null), duration: Durations.medium1, curve: Curves.elasticInOut, child: Text(headerTooltip)),
|
||||||
|
AnimatedDefaultTextStyle(style: TextStyle(fontFamily: "Eurostile Round", color: Color.fromARGB(hoveredPointId == -1 ? 100 : 255, 255, 255, 255), shadows: hoveredPointId != -1 ? textShadow : null), duration: Durations.medium1, curve: Curves.elasticInOut, child: Text(footerTooltip)),
|
||||||
|
],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -1031,7 +1115,7 @@ class _OtherThingy extends StatelessWidget {
|
||||||
)));
|
)));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
result.add(TextSpan(text: " $shit", style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold)));
|
result.add(TextSpan(text: " $shit", style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Colors.white)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -1055,7 +1139,7 @@ class _OtherThingy extends StatelessWidget {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: RichText(
|
title: RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16),
|
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16, color: Colors.white),
|
||||||
text: t.newsParts.leaderboardStart,
|
text: t.newsParts.leaderboardStart,
|
||||||
children: [
|
children: [
|
||||||
TextSpan(text: "№${news.data["rank"]} ", style: const TextStyle(fontWeight: FontWeight.bold)),
|
TextSpan(text: "№${news.data["rank"]} ", style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
@ -1070,7 +1154,7 @@ class _OtherThingy extends StatelessWidget {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: RichText(
|
title: RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16),
|
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16, color: Colors.white),
|
||||||
text: t.newsParts.personalbest,
|
text: t.newsParts.personalbest,
|
||||||
children: [
|
children: [
|
||||||
TextSpan(text: "${gametypes[news.data["gametype"]]} ", style: const TextStyle(fontWeight: FontWeight.bold)),
|
TextSpan(text: "${gametypes[news.data["gametype"]]} ", style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
@ -1093,7 +1177,7 @@ class _OtherThingy extends StatelessWidget {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: RichText(
|
title: RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16),
|
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16, color: Colors.white),
|
||||||
text: t.newsParts.badgeStart,
|
text: t.newsParts.badgeStart,
|
||||||
children: [
|
children: [
|
||||||
TextSpan(text: "${news.data["label"]} ", style: const TextStyle(fontWeight: FontWeight.bold)),
|
TextSpan(text: "${news.data["label"]} ", style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
@ -1115,7 +1199,7 @@ class _OtherThingy extends StatelessWidget {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: RichText(
|
title: RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16),
|
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16, color: Colors.white),
|
||||||
text: t.newsParts.rankupStart,
|
text: t.newsParts.rankupStart,
|
||||||
children: [
|
children: [
|
||||||
TextSpan(text: t.newsParts.rankupMiddle(r: news.data["rank"].toString().toUpperCase()), style: const TextStyle(fontWeight: FontWeight.bold)),
|
TextSpan(text: t.newsParts.rankupMiddle(r: news.data["rank"].toString().toUpperCase()), style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
@ -1137,7 +1221,7 @@ class _OtherThingy extends StatelessWidget {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: RichText(
|
title: RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16),
|
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16, color: Colors.white),
|
||||||
text: t.newsParts.supporterStart,
|
text: t.newsParts.supporterStart,
|
||||||
children: [
|
children: [
|
||||||
TextSpan(text: t.newsParts.tetoSupporter, style: const TextStyle(fontWeight: FontWeight.bold))
|
TextSpan(text: t.newsParts.tetoSupporter, style: const TextStyle(fontWeight: FontWeight.bold))
|
||||||
|
@ -1158,7 +1242,7 @@ class _OtherThingy extends StatelessWidget {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: RichText(
|
title: RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16),
|
style: const TextStyle(fontFamily: 'Eurostile Round', fontSize: 16, color: Colors.white),
|
||||||
text: t.newsParts.supporterGiftStart,
|
text: t.newsParts.supporterGiftStart,
|
||||||
children: [
|
children: [
|
||||||
TextSpan(text: t.newsParts.tetoSupporter, style: const TextStyle(fontWeight: FontWeight.bold))
|
TextSpan(text: t.newsParts.tetoSupporter, style: const TextStyle(fontWeight: FontWeight.bold))
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:tetra_stats/data_objects/tetrio.dart';
|
import 'package:tetra_stats/data_objects/tetrio.dart';
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
import 'package:tetra_stats/views/main_view.dart' show MainView;
|
import 'package:tetra_stats/views/main_view.dart' show MainView;
|
||||||
|
import 'package:tetra_stats/widgets/user_thingy.dart' show textShadow;
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
var _chartsShortTitlesDropdowns = <DropdownMenuItem>[for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value),)];
|
var _chartsShortTitlesDropdowns = <DropdownMenuItem>[for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value),)];
|
||||||
|
@ -31,6 +34,22 @@ class RankView extends StatefulWidget {
|
||||||
class RankState extends State<RankView> with SingleTickerProviderStateMixin {
|
class RankState extends State<RankView> with SingleTickerProviderStateMixin {
|
||||||
late ScrollController _scrollController;
|
late ScrollController _scrollController;
|
||||||
late TabController _tabController;
|
late TabController _tabController;
|
||||||
|
late String previousAxisTitles;
|
||||||
|
late double minX;
|
||||||
|
late double actualMinX;
|
||||||
|
late double maxX;
|
||||||
|
late double actualMaxX;
|
||||||
|
late double minY;
|
||||||
|
late double actualMinY;
|
||||||
|
late double maxY;
|
||||||
|
late double actualMaxY;
|
||||||
|
late double xScale;
|
||||||
|
late double yScale;
|
||||||
|
String headerTooltip = t.pseudoTooltipHeaderInit;
|
||||||
|
String footerTooltip = t.pseudoTooltipFooterInit;
|
||||||
|
int hoveredPointId = -1;
|
||||||
|
double scaleFactor = 5e2;
|
||||||
|
double dragFactor = 7e2;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -41,6 +60,57 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
|
||||||
windowManager.setTitle("Tetra Stats: ${widget.rank[1]["everyone"] ? t.everyoneAverages : t.rankAverages(rank: widget.rank[0].rank.toUpperCase())}");
|
windowManager.setTitle("Tetra Stats: ${widget.rank[1]["everyone"] ? t.everyoneAverages : t.rankAverages(rank: widget.rank[0].rank.toUpperCase())}");
|
||||||
}
|
}
|
||||||
super.initState();
|
super.initState();
|
||||||
|
previousAxisTitles = _chartsX.toString()+_chartsY.toString();
|
||||||
|
recalculateBoundaries();
|
||||||
|
resetScale();
|
||||||
|
}
|
||||||
|
|
||||||
|
void recalculateBoundaries(){
|
||||||
|
actualMinX = (widget.rank[1]["entries"] as List<TetrioPlayerFromLeaderboard>).reduce((value, element) {
|
||||||
|
num n = min(value.getStatByEnum(_chartsX), element.getStatByEnum(_chartsX));
|
||||||
|
if (value.getStatByEnum(_chartsX) == n) {
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}).getStatByEnum(_chartsX) as double;
|
||||||
|
actualMaxX = (widget.rank[1]["entries"] as List<TetrioPlayerFromLeaderboard>).reduce((value, element) {
|
||||||
|
num n = max(value.getStatByEnum(_chartsX), element.getStatByEnum(_chartsX));
|
||||||
|
if (value.getStatByEnum(_chartsX) == n) {
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}).getStatByEnum(_chartsX) as double;
|
||||||
|
actualMinY = (widget.rank[1]["entries"] as List<TetrioPlayerFromLeaderboard>).reduce((value, element) {
|
||||||
|
num n = min(value.getStatByEnum(_chartsY), element.getStatByEnum(_chartsY));
|
||||||
|
if (value.getStatByEnum(_chartsY) == n) {
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}).getStatByEnum(_chartsY) as double;
|
||||||
|
actualMaxY = (widget.rank[1]["entries"] as List<TetrioPlayerFromLeaderboard>).reduce((value, element) {
|
||||||
|
num n = max(value.getStatByEnum(_chartsY), element.getStatByEnum(_chartsY));
|
||||||
|
if (value.getStatByEnum(_chartsY) == n) {
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}).getStatByEnum(_chartsY) as double;
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetScale(){
|
||||||
|
maxX = actualMaxX;
|
||||||
|
minX = actualMinX;
|
||||||
|
maxY = actualMaxY;
|
||||||
|
minY = actualMinY;
|
||||||
|
recalculateScales();
|
||||||
|
}
|
||||||
|
|
||||||
|
void recalculateScales(){
|
||||||
|
xScale = maxX - minX;
|
||||||
|
yScale = maxY - minY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -51,14 +121,50 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void dragHandler(DragUpdateDetails dragUpdDet){
|
||||||
|
setState(() {
|
||||||
|
minX -= (xScale / dragFactor) * dragUpdDet.delta.dx;
|
||||||
|
maxX -= (xScale / dragFactor) * dragUpdDet.delta.dx;
|
||||||
|
minY += (yScale / dragFactor) * dragUpdDet.delta.dy;
|
||||||
|
maxY += (yScale / dragFactor) * dragUpdDet.delta.dy;
|
||||||
|
|
||||||
|
if (minX < actualMinX) {
|
||||||
|
minX = actualMinX;
|
||||||
|
maxX = actualMinX + xScale;
|
||||||
|
}
|
||||||
|
if (maxX > actualMaxX) {
|
||||||
|
maxX = actualMaxX;
|
||||||
|
minX = maxX - xScale;
|
||||||
|
}
|
||||||
|
if(minY < actualMinY){
|
||||||
|
minY = actualMinY;
|
||||||
|
maxY = actualMinY + yScale;
|
||||||
|
}
|
||||||
|
if(maxY > actualMaxY){
|
||||||
|
maxY = actualMaxY;
|
||||||
|
minY = actualMaxY - yScale;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void _justUpdate() {
|
void _justUpdate() {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final t = Translations.of(context);
|
GlobalKey graphKey = GlobalKey();
|
||||||
bool bigScreen = MediaQuery.of(context).size.width > 768;
|
bool bigScreen = MediaQuery.of(context).size.width > 768;
|
||||||
|
EdgeInsets padding = bigScreen ? const EdgeInsets.fromLTRB(40, 40, 40, 48) : const EdgeInsets.fromLTRB(0, 40, 16, 48);
|
||||||
|
double graphStartX = padding.left;
|
||||||
|
double graphEndX = MediaQuery.sizeOf(context).width - padding.right;
|
||||||
|
if (previousAxisTitles != _chartsX.toString()+_chartsY.toString()){
|
||||||
|
recalculateBoundaries();
|
||||||
|
resetScale();
|
||||||
|
previousAxisTitles = _chartsX.toString()+_chartsY.toString();
|
||||||
|
print(padding);
|
||||||
|
};
|
||||||
|
final t = Translations.of(context);
|
||||||
List<TetrioPlayerFromLeaderboard> they = TetrioPlayersLeaderboard("lol", []).getStatRanking(widget.rank[1]["entries"]!, _sortBy, reversed: _reversed, country: _country);
|
List<TetrioPlayerFromLeaderboard> they = TetrioPlayersLeaderboard("lol", []).getStatRanking(widget.rank[1]["entries"]!, _sortBy, reversed: _reversed, country: _country);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
@ -69,9 +175,8 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
|
||||||
child: NestedScrollView(
|
child: NestedScrollView(
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
headerSliverBuilder: (context, value) {
|
headerSliverBuilder: (context, value) {
|
||||||
return [
|
return [ SliverToBoxAdapter(
|
||||||
SliverToBoxAdapter(
|
child: Column(
|
||||||
child: Column(
|
|
||||||
children: [
|
children: [
|
||||||
Flex(
|
Flex(
|
||||||
direction: Axis.vertical,
|
direction: Axis.vertical,
|
||||||
|
@ -131,11 +236,9 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
const Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: EdgeInsets.all(8.0),
|
||||||
child: Text(t.currentAxis(axis: "X"),
|
child: Text("X:", style: TextStyle(fontSize: 22))),
|
||||||
style:
|
|
||||||
const TextStyle(fontSize: 22))),
|
|
||||||
DropdownButton(
|
DropdownButton(
|
||||||
items: _chartsShortTitlesDropdowns,
|
items: _chartsShortTitlesDropdowns,
|
||||||
value: _chartsX,
|
value: _chartsX,
|
||||||
|
@ -152,10 +255,9 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
const Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: EdgeInsets.all(8.0),
|
||||||
child: Text(t.currentAxis(axis: "Y"),
|
child: Text("Y:", style: TextStyle(fontSize: 22)),
|
||||||
style: const TextStyle(fontSize: 22)),
|
|
||||||
),
|
),
|
||||||
DropdownButton(
|
DropdownButton(
|
||||||
items: _chartsShortTitlesDropdowns,
|
items: _chartsShortTitlesDropdowns,
|
||||||
|
@ -174,79 +276,109 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: MediaQuery.of(context).size.width,
|
width: MediaQuery.of(context).size.width,
|
||||||
height: MediaQuery.of(context).size.height - 104,
|
height: MediaQuery.of(context).size.height - 104,
|
||||||
child: Stack(
|
child: Listener(
|
||||||
children: [
|
behavior: HitTestBehavior.translucent,
|
||||||
Padding(
|
onPointerSignal: (signal) {
|
||||||
padding: bigScreen
|
if (signal is PointerScrollEvent) {
|
||||||
? const EdgeInsets.fromLTRB(
|
RenderBox graphBox = graphKey.currentContext?.findRenderObject() as RenderBox;
|
||||||
40, 40, 40, 48)
|
Offset graphPosition = graphBox.localToGlobal(Offset.zero);
|
||||||
: const EdgeInsets.fromLTRB(
|
double scrollPosRelativeX = (signal.position.dx - graphStartX) / (graphEndX - graphStartX);
|
||||||
0, 40, 16, 48),
|
double scrollPosRelativeY = (signal.position.dy - graphPosition.dy) / (graphBox.size.height - 30); // size - bottom titles height
|
||||||
child: ScatterChart(
|
double newMinX, newMaxX, newMinY, newMaxY;
|
||||||
ScatterChartData(
|
newMinX = minX - (xScale / scaleFactor) * signal.scrollDelta.dy * scrollPosRelativeX;
|
||||||
scatterSpots: [
|
newMaxX = maxX + (xScale / scaleFactor) * signal.scrollDelta.dy * (1-scrollPosRelativeX);
|
||||||
for (TetrioPlayerFromLeaderboard entry
|
newMinY = minY - (yScale / scaleFactor) * signal.scrollDelta.dy * (1-scrollPosRelativeY);
|
||||||
in widget.rank[1]["entries"])
|
newMaxY = maxY + (yScale / scaleFactor) * signal.scrollDelta.dy * scrollPosRelativeY;
|
||||||
_MyScatterSpot(
|
if ((newMaxX - newMinX).isNegative) return;
|
||||||
entry.getStatByEnum(_chartsX)
|
if ((newMaxY - newMinY).isNegative) return;
|
||||||
as double,
|
setState(() {
|
||||||
entry.getStatByEnum(_chartsY)
|
minX = max(newMinX, actualMinX);
|
||||||
as double,
|
maxX = min(newMaxX, actualMaxX);
|
||||||
entry.userId,
|
minY = max(newMinY, actualMinY);
|
||||||
entry.username,
|
maxY = min(newMaxY, actualMaxY);
|
||||||
dotPainter: FlDotCirclePainter(color: rankColors[entry.rank]??Colors.white, radius: 3))
|
recalculateScales();
|
||||||
],
|
_scrollController.jumpTo(_scrollController.position.maxScrollExtent - signal.scrollDelta.dy);
|
||||||
scatterTouchData: ScatterTouchData(
|
});
|
||||||
touchTooltipData:
|
}},
|
||||||
ScatterTouchTooltipData(
|
child: GestureDetector(
|
||||||
fitInsideHorizontally: true,
|
behavior: HitTestBehavior.opaque,
|
||||||
fitInsideVertically: true,
|
onDoubleTap: () {
|
||||||
getTooltipItems:
|
setState(() {
|
||||||
(touchedSpot) {
|
minX = actualMinX;
|
||||||
touchedSpot
|
maxX = actualMaxX;
|
||||||
as _MyScatterSpot;
|
minY = actualMinY;
|
||||||
return ScatterTooltipItem(
|
maxY = actualMaxY;
|
||||||
"${touchedSpot.nickname}\n",
|
recalculateScales();
|
||||||
textStyle: const TextStyle(
|
});
|
||||||
fontFamily:
|
},
|
||||||
"Eurostile Round Extended"),
|
// TODO: Figure out wtf is going on with gestures
|
||||||
children: [
|
child: Padding(
|
||||||
TextSpan(
|
padding: bigScreen ? const EdgeInsets.fromLTRB(40, 40, 40, 48) : const EdgeInsets.fromLTRB(0, 40, 16, 48),
|
||||||
text:
|
child: Stack(
|
||||||
"${_f4.format(touchedSpot.x)} ${chartsShortTitles[_chartsX]}\n${_f4.format(touchedSpot.y)} ${chartsShortTitles[_chartsY]}",
|
children: [
|
||||||
style: const TextStyle(
|
ScatterChart(
|
||||||
fontFamily:
|
key: graphKey,
|
||||||
"Eurostile Round"))
|
ScatterChartData(
|
||||||
]);
|
minX: minX,
|
||||||
}),
|
maxX: maxX,
|
||||||
touchCallback: (event, response) {
|
minY: minY,
|
||||||
if (event.runtimeType ==
|
maxY: maxY,
|
||||||
FlTapDownEvent &&
|
clipData: const FlClipData.all(),
|
||||||
response?.touchedSpot?.spot !=
|
scatterSpots: [
|
||||||
null) {
|
for (TetrioPlayerFromLeaderboard entry in widget.rank[1]["entries"])
|
||||||
var spot = response?.touchedSpot
|
if (entry.apm != 0.0 && entry.vs != 0.0) // prevents from ScatterChart "Offset argument contained a NaN value." exception
|
||||||
?.spot as _MyScatterSpot;
|
_MyScatterSpot(
|
||||||
Navigator.push(
|
entry.getStatByEnum(_chartsX) as double,
|
||||||
context,
|
entry.getStatByEnum(_chartsY) as double,
|
||||||
MaterialPageRoute(
|
entry.userId,
|
||||||
builder: (context) =>
|
entry.username,
|
||||||
MainView(
|
dotPainter: FlDotCirclePainter(color: rankColors[entry.rank]??Colors.white, radius: 3))
|
||||||
player:
|
],
|
||||||
spot.nickname),
|
scatterTouchData: ScatterTouchData(
|
||||||
maintainState: false,
|
handleBuiltInTouches: false,
|
||||||
),
|
touchCallback:(touchEvent, touchResponse) {
|
||||||
);
|
if (touchEvent is FlPanUpdateEvent){
|
||||||
}
|
dragHandler(touchEvent.details);
|
||||||
},
|
return;
|
||||||
|
}
|
||||||
|
if (touchEvent is FlPointerHoverEvent){
|
||||||
|
setState(() {
|
||||||
|
if (touchResponse?.touchedSpot == null) {
|
||||||
|
hoveredPointId = -1;
|
||||||
|
} else {
|
||||||
|
hoveredPointId = touchResponse!.touchedSpot!.spotIndex;
|
||||||
|
_MyScatterSpot castedPoint = touchResponse.touchedSpot!.spot as _MyScatterSpot;
|
||||||
|
headerTooltip = castedPoint.nickname;
|
||||||
|
footerTooltip = "${_f4.format(castedPoint.x)} ${chartsShortTitles[_chartsX]}; ${_f4.format(castedPoint.y)} ${chartsShortTitles[_chartsY]}";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (touchEvent is FlPointerExitEvent){
|
||||||
|
setState(() {hoveredPointId = -1;});
|
||||||
|
}
|
||||||
|
if (touchEvent is FlTapUpEvent && touchResponse?.touchedSpot?.spot != null){
|
||||||
|
_MyScatterSpot spot = touchResponse!.touchedSpot!.spot as _MyScatterSpot;
|
||||||
|
Navigator.push(context, MaterialPageRoute(builder: (context) => MainView(player: spot.nickname), maintainState: false));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
swapAnimationDuration: const Duration(milliseconds: 150), // Optional
|
||||||
|
swapAnimationCurve: Curves.linear, // Optional
|
||||||
),
|
),
|
||||||
),
|
Padding(
|
||||||
swapAnimationDuration: const Duration(
|
padding: EdgeInsets.fromLTRB(graphStartX+8, padding.top/2+8, 0, 0),
|
||||||
milliseconds: 150), // Optional
|
child: Column(
|
||||||
swapAnimationCurve:
|
children: [
|
||||||
Curves.linear, // Optional
|
AnimatedDefaultTextStyle(style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 24, color: Color.fromARGB(hoveredPointId == -1 ? 100 : 255, 255, 255, 255), shadows: hoveredPointId != -1 ? textShadow : null), duration: Durations.medium1, curve: Curves.elasticInOut, child: Text(headerTooltip)),
|
||||||
|
AnimatedDefaultTextStyle(style: TextStyle(fontFamily: "Eurostile Round", color: Color.fromARGB(hoveredPointId == -1 ? 100 : 255, 255, 255, 255), shadows: hoveredPointId != -1 ? textShadow : null), duration: Durations.medium1, curve: Curves.elasticInOut, child: Text(footerTooltip)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
))
|
))
|
||||||
else Center(child: Text(t.notEnoughData, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)))
|
else Center(child: Text(t.notEnoughData, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)))
|
||||||
],
|
],
|
||||||
|
|
|
@ -197,24 +197,24 @@ class TlMatchResultState extends State<TlMatchResultView> {
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(child: FutureBuilder(future: replayData, builder: (context, snapshot) {
|
SliverToBoxAdapter(child: FutureBuilder(future: replayData, builder: (context, snapshot) {
|
||||||
switch(snapshot.connectionState){
|
switch(snapshot.connectionState){
|
||||||
case ConnectionState.none:
|
case ConnectionState.none:
|
||||||
case ConnectionState.waiting:
|
case ConnectionState.waiting:
|
||||||
case ConnectionState.active:
|
case ConnectionState.active:
|
||||||
return const LinearProgressIndicator();
|
return const LinearProgressIndicator();
|
||||||
case ConnectionState.done:
|
case ConnectionState.done:
|
||||||
if (!snapshot.hasError){
|
if (!snapshot.hasError){
|
||||||
if (roundSelector.isNegative){
|
if (roundSelector.isNegative){
|
||||||
var time = framesToTime(snapshot.data!.totalLength);
|
var time = framesToTime(snapshot.data!.totalLength);
|
||||||
return Center(child: Text("Match Length: ${time.inMinutes}:${secs.format(time.inMicroseconds /1000000 % 60)}", textAlign: TextAlign.center));
|
return Center(child: Text("${t.matchLength}: ${time.inMinutes}:${secs.format(time.inMicroseconds /1000000 % 60)}", textAlign: TextAlign.center));
|
||||||
}else{
|
}else{
|
||||||
var time = framesToTime(snapshot.data!.roundLengths[roundSelector]);
|
var time = framesToTime(snapshot.data!.roundLengths[roundSelector]);
|
||||||
return Center(child: Text("Round Length: ${time.inMinutes}:${secs.format(time.inMicroseconds /1000000 % 60)}\nWinner: ${snapshot.data!.roundWinners[roundSelector][1]}", textAlign: TextAlign.center,));
|
return Center(child: Text("${t.roundLength}: ${time.inMinutes}:${secs.format(time.inMicroseconds /1000000 % 60)}\n${t.winner}: ${snapshot.data!.roundWinners[roundSelector][1]}", textAlign: TextAlign.center,));
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
return const Text("skill issue", textAlign: TextAlign.center);
|
return Text("${snapshot.error.toString()}\n${snapshot.stackTrace}", textAlign: TextAlign.center);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
},),),
|
},),),
|
||||||
const SliverToBoxAdapter(
|
const SliverToBoxAdapter(
|
||||||
child: Divider(),
|
child: Divider(),
|
||||||
|
@ -480,7 +480,7 @@ class TlMatchResultState extends State<TlMatchResultView> {
|
||||||
CompareBoolThingy(
|
CompareBoolThingy(
|
||||||
greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).handling.safeLock,
|
greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).handling.safeLock,
|
||||||
redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).handling.safeLock,
|
redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).handling.safeLock,
|
||||||
label: "safeLock",
|
label: "Safe HD",
|
||||||
trueIsBetter: true)
|
trueIsBetter: true)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,7 +12,7 @@ class StatCellNum extends StatelessWidget {
|
||||||
this.fractionDigits,
|
this.fractionDigits,
|
||||||
this.oldPlayerStat,
|
this.oldPlayerStat,
|
||||||
required this.higherIsBetter,
|
required this.higherIsBetter,
|
||||||
this.okText});
|
this.okText, this.alertTitle});
|
||||||
|
|
||||||
final num playerStat;
|
final num playerStat;
|
||||||
final num? oldPlayerStat;
|
final num? oldPlayerStat;
|
||||||
|
@ -20,6 +20,7 @@ class StatCellNum extends StatelessWidget {
|
||||||
final String playerStatLabel;
|
final String playerStatLabel;
|
||||||
final String? okText;
|
final String? okText;
|
||||||
final bool isScreenBig;
|
final bool isScreenBig;
|
||||||
|
final String? alertTitle;
|
||||||
final List<Widget>? alertWidgets;
|
final List<Widget>? alertWidgets;
|
||||||
final int? fractionDigits;
|
final int? fractionDigits;
|
||||||
|
|
||||||
|
@ -43,6 +44,7 @@ class StatCellNum extends StatelessWidget {
|
||||||
fontFamily: "Eurostile Round Extended",
|
fontFamily: "Eurostile Round Extended",
|
||||||
//fontWeight: FontWeight.bold,
|
//fontWeight: FontWeight.bold,
|
||||||
fontSize: isScreenBig ? 32 : 24,
|
fontSize: isScreenBig ? 32 : 24,
|
||||||
|
color: Colors.white
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
@ -66,7 +68,7 @@ class StatCellNum extends StatelessWidget {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) => AlertDialog(
|
builder: (BuildContext context) => AlertDialog(
|
||||||
title: Text(playerStatLabel.replaceAll(RegExp(r'\n'), " "),
|
title: Text(alertTitle??playerStatLabel.replaceAll(RegExp(r'\n'), " "),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontFamily: "Eurostile Round Extended")),
|
fontFamily: "Eurostile Round Extended")),
|
||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
|
|
|
@ -345,7 +345,7 @@ class _TLThingyState extends State<TLThingy> {
|
||||||
oldPlayerStat: oldTl?.nerdStats?.cheese,),
|
oldPlayerStat: oldTl?.nerdStats?.cheese,),
|
||||||
StatCellNum(playerStat: currentTl.nerdStats!.gbe, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.gbe,
|
StatCellNum(playerStat: currentTl.nerdStats!.gbe, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.gbe,
|
||||||
alertWidgets: [Text(t.statCellNum.gbeDescription),
|
alertWidgets: [Text(t.statCellNum.gbeDescription),
|
||||||
Text("${t.formula}: ((APP * DS/S) / PPS) * 2"),
|
Text("${t.formula}: APP * DS/P * 2"),
|
||||||
Text("${t.exactValue}: ${currentTl.nerdStats!.gbe}"),],
|
Text("${t.exactValue}: ${currentTl.nerdStats!.gbe}"),],
|
||||||
okText: t.popupActions.ok,
|
okText: t.popupActions.ok,
|
||||||
higherIsBetter: true,
|
higherIsBetter: true,
|
||||||
|
|
|
@ -23,6 +23,11 @@ Future<void> copyToClipboard(String text) async {
|
||||||
await Clipboard.setData(ClipboardData(text: text));
|
await Clipboard.setData(ClipboardData(text: text));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Shadow> textShadow = const <Shadow>[
|
||||||
|
Shadow(offset: Offset(0.0, 0.0), blurRadius: 3.0, color: Colors.black),
|
||||||
|
Shadow(offset: Offset(0.0, 0.0), blurRadius: 8.0, color: Colors.black),
|
||||||
|
];
|
||||||
|
|
||||||
class UserThingy extends StatelessWidget {
|
class UserThingy extends StatelessWidget {
|
||||||
final TetrioPlayer player;
|
final TetrioPlayer player;
|
||||||
final bool showStateTimestamp;
|
final bool showStateTimestamp;
|
||||||
|
@ -109,18 +114,7 @@ class UserThingy extends StatelessWidget {
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontFamily: "Eurostile Round Extended",
|
fontFamily: "Eurostile Round Extended",
|
||||||
fontSize: bigScreen ? 42 : 28,
|
fontSize: bigScreen ? 42 : 28,
|
||||||
shadows: const <Shadow>[
|
shadows: textShadow,
|
||||||
Shadow(
|
|
||||||
offset: Offset(0.0, 0.0),
|
|
||||||
blurRadius: 3.0,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
Shadow(
|
|
||||||
offset: Offset(0.0, 0.0),
|
|
||||||
blurRadius: 8.0,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)),
|
)),
|
||||||
TextButton(
|
TextButton(
|
||||||
child: Text(player.userId, style: const TextStyle(fontFamily: "Eurostile Round Condensed", fontSize: 14)),
|
child: Text(player.userId, style: const TextStyle(fontFamily: "Eurostile Round Condensed", fontSize: 14)),
|
||||||
|
@ -280,7 +274,8 @@ class UserThingy extends StatelessWidget {
|
||||||
playerStat: player.gameTime.inHours,
|
playerStat: player.gameTime.inHours,
|
||||||
playerStatLabel: t.statCellNum.hoursPlayed,
|
playerStatLabel: t.statCellNum.hoursPlayed,
|
||||||
isScreenBig: bigScreen,
|
isScreenBig: bigScreen,
|
||||||
alertWidgets: [Text("${t.exactGametime}: ${player.gameTime.toString()}")],
|
alertTitle: t.exactGametime,
|
||||||
|
alertWidgets: [Text(player.gameTime.toString(), style: TextStyle(fontFamily: "Eurostile Round Extended"),)],
|
||||||
higherIsBetter: true,),
|
higherIsBetter: true,),
|
||||||
if (player.gamesPlayed >= 0)
|
if (player.gamesPlayed >= 0)
|
||||||
StatCellNum(
|
StatCellNum(
|
||||||
|
|
|
@ -2,7 +2,7 @@ name: tetra_stats
|
||||||
description: Track your and other player stats in TETR.IO
|
description: Track your and other player stats in TETR.IO
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 1.3.0+13
|
version: 1.4.0+14
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0'
|
sdk: '>=3.0.0'
|
||||||
|
|
|
@ -47,6 +47,8 @@
|
||||||
"anonRecord": "Guests are not allowed to set records",
|
"anonRecord": "Guests are not allowed to set records",
|
||||||
"notEnoughData": "Not enough data",
|
"notEnoughData": "Not enough data",
|
||||||
"noHistorySaved": "No history saved",
|
"noHistorySaved": "No history saved",
|
||||||
|
"pseudoTooltipHeaderInit": "Hover over point",
|
||||||
|
"pseudoTooltipFooterInit": "to see detailed data",
|
||||||
"obtainDate": "Obtained ${date}",
|
"obtainDate": "Obtained ${date}",
|
||||||
"fetchDate": "Fetched ${date}",
|
"fetchDate": "Fetched ${date}",
|
||||||
"exactGametime": "Exact gametime",
|
"exactGametime": "Exact gametime",
|
||||||
|
@ -115,6 +117,9 @@
|
||||||
"match": "Match",
|
"match": "Match",
|
||||||
"roundNumber": "Round $n",
|
"roundNumber": "Round $n",
|
||||||
"statsFor": "Stats for",
|
"statsFor": "Stats for",
|
||||||
|
"matchLength": "Match Length",
|
||||||
|
"roundLength": "Round Length",
|
||||||
|
"winner": "Winner",
|
||||||
"registred": "Registred",
|
"registred": "Registred",
|
||||||
"playedTL": "Played Tetra League",
|
"playedTL": "Played Tetra League",
|
||||||
"winChance": "Win Chance",
|
"winChance": "Win Chance",
|
||||||
|
|
|
@ -47,6 +47,8 @@
|
||||||
"anonRecord": "Гостям нельзя ставить рекорды",
|
"anonRecord": "Гостям нельзя ставить рекорды",
|
||||||
"notEnoughData": "Недостаточно данных",
|
"notEnoughData": "Недостаточно данных",
|
||||||
"noHistorySaved": "Нет сохранённой истории",
|
"noHistorySaved": "Нет сохранённой истории",
|
||||||
|
"pseudoTooltipHeaderInit": "Наведите курсор на точку",
|
||||||
|
"pseudoTooltipFooterInit": "чтобы узнать подробности",
|
||||||
"obtainDate": "Получено ${date}",
|
"obtainDate": "Получено ${date}",
|
||||||
"fetchDate": "На момент ${date}",
|
"fetchDate": "На момент ${date}",
|
||||||
"exactGametime": "Время, проведённое в игре",
|
"exactGametime": "Время, проведённое в игре",
|
||||||
|
@ -115,6 +117,9 @@
|
||||||
"match": "Матч",
|
"match": "Матч",
|
||||||
"roundNumber": "Раунд $n",
|
"roundNumber": "Раунд $n",
|
||||||
"statsFor": "Статистика за",
|
"statsFor": "Статистика за",
|
||||||
|
"matchLength": "Продолжительность матча",
|
||||||
|
"roundLength": "Продолжительность раунда",
|
||||||
|
"winner": "Победитель",
|
||||||
"registred": "Зарегистрирован",
|
"registred": "Зарегистрирован",
|
||||||
"playedTL": "Играл в Тетра Лигу",
|
"playedTL": "Играл в Тетра Лигу",
|
||||||
"winChance": "Шансы на победу",
|
"winChance": "Шансы на победу",
|
||||||
|
|
Loading…
Reference in New Issue