Compare commits

..

No commits in common. "main" and "0.0.32+1" have entirely different histories.

39 changed files with 300 additions and 1172 deletions

View file

@ -1,58 +1,3 @@
# dth-pingpong-mobileapp
>
> Minimum supported Android API: 28 (Android 9).\
> Minimum required iOS version: 14
>
Flutter application for ping pong score tracking. supports friends, elo tracking, match creation and planning.
![lang](https://img.shields.io/badge/Flutter-02569B?style=for-the-badge&logo=flutter&logoColor=white) ![os1](https://img.shields.io/badge/Android-3DDC84?style=for-the-badge&logo=android&logoColor=white)
---
# Installing
Installation options are available for major mobile platforms. Although we mainly support android, all the features are cross-compatible with iOS
### Stable release channel (Android only)
Grab one of the builds from the releases page or run:
```bash
flutter build apk
```
Production artifacts will be found in the `build\app\outputs\apk\` directory.
**RRC (Rolling Release Channel)**
There's also a Rolling release available as pre-release builds. They will be released in a much quicker update ring (every other commit i'd assume) and are mostly debug builds so they'll be very heavy and *likely* broken.
### Building instructions (Android)
Make sure you have [Flutter](https://flutter.dev/), [Android Studio](https://developer.android.com/studio), and [Visual Studio Code](https://code.visualstudio.com/) installed. Install the suggested Flutter, Dart, and linting extensions in Visual Studio Code.
Run the following commands to ensure all dependencies are met:
```bash
flutter doctor
```
You'll be guided through a first-time setup that checks for platform tools, dependencies, and missing components.
Once the setup is complete you can navigate to the repo folder and run
```bash
flutter run
```
If you want to build debug you can also run
```bash
flutter build apk --debug
```
Build artifacts for Android are saved under `build\app\outputs\apk`.
### Building instructions (iOS)
**MAJOR WORD OF WARNING: iOS builds are NOT officially supported, and we have NO clue as to why or how certain things just break on the xCode build (namely some icons and some api calls). if you encounter these issues you are free to patch them, and, if you seem fit, hit me up with a PR**
Make sure you have [Flutter](https://flutter.dev/), [xCode and Apple Developer Tools](https://apps.apple.com/en/app/xcode/id497799835?mt=12), [cocoapods](https://formulae.brew.sh/formula/cocoapods), and [Visual Studio Code](https://code.visualstudio.com/) installed.
Install the suggested Flutter, Dart, and linting extensions in Visual Studio Code and set up xcode for "iOS on iphone" developement. Once that is done, go in the project's folder and run the following command to ensure that your build environment is properly configured, any missing component will be highlighted
```bash
flutter doctor
```
Once you've done that, from the same terminal, run this command to generate a .IPA file - once you're done it's a matter of sideloading it to your device. since i've never owned an apple device i have no idea how to do that. iOS artifacts are saved in `build/ios/runner/`.
```bash
flutter build ios
```
Mobile application for ping pong score tracking. supports friends ,elo tracking, match creation and planning.

View file

@ -3,8 +3,6 @@ analyzer:
errors:
library_private_types_in_public_api: ignore
prefer_const_constructors: ignore
prefer_const_literals_to_create_immutables: ignore
prefer_final_fields: ignore
use_build_context_synchronously: ignore
use_key_in_widget_constructors: ignore
use_super_parameters: ignore

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

BIN
ios/.DS_Store vendored

Binary file not shown.

View file

@ -1,2 +1 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

View file

@ -1,2 +1 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

View file

@ -1,44 +0,0 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '12.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end

View file

@ -1,23 +0,0 @@
PODS:
- Flutter (1.0.0)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
DEPENDENCIES:
- Flutter (from `Flutter`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
EXTERNAL SOURCES:
Flutter:
:path: Flutter
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
SPEC CHECKSUMS:
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
COCOAPODS: 1.16.2

View file

@ -10,9 +10,7 @@
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
4ECA18A68D772963A75CDDEE /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 664C6953382FB54D8ADFAA15 /* Pods_Runner.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
860A7C4827A4A1FA258EE3B0 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A33A52767F410023C3017843 /* Pods_RunnerTests.framework */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
@ -44,19 +42,12 @@
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
1D683FDEB78EA33536EDD9B0 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
2310337C49F4840CC13DECEC /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
2BD08DFC2DFC2958AE95FAD5 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
664C6953382FB54D8ADFAA15 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7339FE983199E9675039DE4E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
8D54E1F7566A817BB3B988AC /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
9702D4B740EAB4624B982FE0 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -64,38 +55,19 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
A33A52767F410023C3017843 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
035979494952341CA3553103 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
860A7C4827A4A1FA258EE3B0 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
4ECA18A68D772963A75CDDEE /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
2F1367A0823D5A1C2FD30B1E /* Frameworks */ = {
isa = PBXGroup;
children = (
664C6953382FB54D8ADFAA15 /* Pods_Runner.framework */,
A33A52767F410023C3017843 /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
@ -104,20 +76,6 @@
path = RunnerTests;
sourceTree = "<group>";
};
803619556C668AF928FE7E28 /* Pods */ = {
isa = PBXGroup;
children = (
2310337C49F4840CC13DECEC /* Pods-Runner.debug.xcconfig */,
7339FE983199E9675039DE4E /* Pods-Runner.release.xcconfig */,
2BD08DFC2DFC2958AE95FAD5 /* Pods-Runner.profile.xcconfig */,
1D683FDEB78EA33536EDD9B0 /* Pods-RunnerTests.debug.xcconfig */,
8D54E1F7566A817BB3B988AC /* Pods-RunnerTests.release.xcconfig */,
9702D4B740EAB4624B982FE0 /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
@ -136,8 +94,6 @@
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
803619556C668AF928FE7E28 /* Pods */,
2F1367A0823D5A1C2FD30B1E /* Frameworks */,
);
sourceTree = "<group>";
};
@ -172,10 +128,8 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
B52AA023DC4D626077351632 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
035979494952341CA3553103 /* Frameworks */,
);
buildRules = (
);
@ -191,14 +145,12 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
B9DF9E6804910D262BD7E7EA /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
64EEB9E634C86FA592F486D2 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@ -286,23 +238,6 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
64EEB9E634C86FA592F486D2 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@ -318,50 +253,6 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
B52AA023DC4D626077351632 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
B9DF9E6804910D262BD7E7EA /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@ -436,7 +327,6 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@ -471,10 +361,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = R28MH57GSC;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@ -483,7 +370,6 @@
);
PRODUCT_BUNDLE_IDENTIFIER = dth.innodesi.pingpongapp;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
@ -492,7 +378,6 @@
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 1D683FDEB78EA33536EDD9B0 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@ -510,7 +395,6 @@
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 8D54E1F7566A817BB3B988AC /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@ -526,7 +410,6 @@
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9702D4B740EAB4624B982FE0 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@ -564,7 +447,6 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@ -622,7 +504,6 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@ -659,10 +540,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = R28MH57GSC;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@ -671,7 +549,6 @@
);
PRODUCT_BUNDLE_IDENTIFIER = dth.innodesi.pingpongapp;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@ -685,10 +562,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = R28MH57GSC;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@ -697,7 +571,6 @@
);
PRODUCT_BUNDLE_IDENTIFIER = dth.innodesi.pingpongapp;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";

View file

@ -4,7 +4,4 @@
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View file

@ -1,7 +1,7 @@
import Flutter
import UIKit
@main
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,

View file

@ -1,3 +0,0 @@
// lib/globals.dart
const String apiurl = "https://api.dthpp.mercurio.moe";
//const String apiurl = "http://192.168.1.120:9134";

View file

@ -17,7 +17,7 @@ class MyApp extends StatelessWidget {
title: 'Ping Pong Tracker',
theme: ThemeData.dark(useMaterial3: true).copyWith(
colorScheme: ColorScheme.dark(
primary: Colors.blue.shade800,
primary: Colors.blue.shade900,
),
),
home: EntryPoint(),

View file

@ -7,9 +7,6 @@ import 'views/joinmatch.dart';
import 'views/creatematch.dart';
import 'views/friendlist.dart';
import 'views/myprofile.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import '../globals.dart';
class HomePage extends StatefulWidget {
@override
@ -19,6 +16,7 @@ class HomePage extends StatefulWidget {
class _HomePageState extends State<HomePage> {
int _selectedIndex = 0;
// Define the pages for each section
final List<Widget> _pages = [
LeaderboardPage(),
JoinMatchPage(),
@ -42,70 +40,12 @@ class _HomePageState extends State<HomePage> {
);
}
Future<Map<String, String>> fetchCommitHashes() async {
const apiUrl = '$apiurl/version';
try {
final response = await http.get(Uri.parse(apiUrl));
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
String formatHash(String? hash) {
if (hash == null) return 'Unknown';
return '#${hash.substring(0, 8).toUpperCase()}';
}
return {
'backend': formatHash(data['backend']),
'frontend': formatHash(data['frontend']),
};
} else {
throw Exception('Failed to fetch commit hashes');
}
} catch (e) {
return {
'backend': 'Error fetching hash',
'frontend': 'Error fetching hash',
};
}
}
Future<void> _showOpenSourceLicenses() async {
final commitHashes = await fetchCommitHashes();
showDialog(
context: context,
builder: (BuildContext context) => AboutDialog(
applicationIcon: const Icon(Icons.code),
applicationLegalese: '© 2024 Thomas Bassi @ Defence Tech.',
applicationName: 'DTHPP',
applicationVersion:
'API: ${commitHashes['backend']} - UI: ${commitHashes['frontend']}',
children: [
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: Text(
"Do people even care about licenses? Is this ever going to be opened? Anywho, i hope you're enjoying the app and having fun on your break :D",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w400),
),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Ping Pong Tracker'),
actions: [
IconButton(
icon: Icon(Icons.info),
onPressed: _showOpenSourceLicenses,
tooltip: 'Open Source Licenses',
),
IconButton(
icon: Icon(Icons.logout),
onPressed: () => _logout(context),

View file

@ -3,9 +3,7 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'home.dart';
import '../globals.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:url_launcher/url_launcher.dart';
class LoginPage extends StatefulWidget {
@override
@ -20,20 +18,11 @@ class _LoginPageState extends State<LoginPage> {
bool _isLogin = true;
bool _isLoading = false;
final String baseUrl = 'http://api.dthpp.mercurio.moe';
Future<void> _handleAuth() async {
final email = _emailController.text.trim();
final password = _passwordController.text.trim();
final displayName = _displayNameController.text.trim();
// Input validation
if (email.isEmpty ||
password.isEmpty ||
(!_isLogin && displayName.isEmpty)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Please fill in all required fields.')),
);
return;
}
setState(() {
_isLoading = true;
@ -49,13 +38,13 @@ class _LoginPageState extends State<LoginPage> {
context, MaterialPageRoute(builder: (context) => HomePage()));
}
} else {
final uid = await _register(email, password, displayName);
final uid = await _register(email, password, _displayNameController.text.trim());
if (uid != null) {
setState(() {
_isLogin = true;
});
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Registration successful! Please login.')));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Registration successful! Please login.')));
}
}
} catch (e) {
@ -69,7 +58,7 @@ class _LoginPageState extends State<LoginPage> {
}
Future<String?> _login(String email, String password) async {
final url = Uri.parse('$apiurl/login');
final url = Uri.parse('$baseUrl/login');
final response = await http.post(
url,
headers: {'Content-Type': 'application/json'},
@ -84,9 +73,8 @@ class _LoginPageState extends State<LoginPage> {
}
}
Future<String?> _register(
String email, String password, String displayName) async {
final url = Uri.parse('$apiurl/register');
Future<String?> _register(String email, String password, String displayName) async {
final url = Uri.parse('$baseUrl/register');
final response = await http.post(
url,
headers: {'Content-Type': 'application/json'},
@ -130,61 +118,21 @@ class _LoginPageState extends State<LoginPage> {
decoration: InputDecoration(labelText: 'Display Name'),
),
const SizedBox(height: 20),
if (_isLoading)
CircularProgressIndicator()
else
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: _handleAuth,
child: Text(_isLogin ? 'Login' : 'Register'),
),
ElevatedButton(
onPressed: () {
setState(() {
_isLogin = !_isLogin;
});
},
child: Text(_isLogin ? 'Register' : 'Back to Login'),
),
],
),
const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
icon: Icon(FontAwesomeIcons.github),
onPressed: () async {
final url = Uri.parse(
'https://git.mercurio.moe/Mercury/dth-pingpong-mobileapp');
if (await canLaunchUrl(url)) {
await launchUrl(
url,
mode: LaunchMode
.externalApplication, // Ensures it opens in the browser
);
} else {
throw 'Could not launch $url';
}
}),
IconButton(
icon: Icon(FontAwesomeIcons.chartSimple),
onPressed: () async {
final url =
Uri.parse('https://kuma.mercurio.moe/status/dthpp');
if (await canLaunchUrl(url)) {
await launchUrl(
url,
mode: LaunchMode
.externalApplication, // Ensures it opens in the browser
);
} else {
throw 'Could not launch $url';
}
}),
],
_isLoading
? CircularProgressIndicator()
: ElevatedButton(
onPressed: _handleAuth,
child: Text(_isLogin ? 'Login' : 'Register'),
),
TextButton(
onPressed: () {
setState(() {
_isLogin = !_isLogin;
});
},
child: Text(_isLogin
? 'Don\'t have an account? Register'
: 'Already have an account? Login'),
),
],
),

View file

@ -2,7 +2,6 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import '../../globals.dart';
class CreateMatchPage extends StatefulWidget {
@override
@ -12,11 +11,10 @@ class CreateMatchPage extends StatefulWidget {
class _CreateMatchPageState extends State<CreateMatchPage> {
String? _matchId;
bool _isLoading = false;
bool _isTwoPlayerModeEnabled = false;
final String _createMatchApiUrl = '$apiurl/creatematch';
final String _createDoubleMatchUrl = '$apiurl/creatematch_2v2';
final String _createMatchApiUrl = 'http://api.dthpp.mercurio.moe/creatematch'; // Replace with your API endpoint
// Method to create a match
Future<void> _createMatch() async {
setState(() {
_isLoading = true;
@ -34,10 +32,8 @@ class _CreateMatchPageState extends State<CreateMatchPage> {
}
try {
final String apiUrl =
_isTwoPlayerModeEnabled ? _createDoubleMatchUrl : _createMatchApiUrl;
final response = await http.post(
Uri.parse(apiUrl),
Uri.parse(_createMatchApiUrl),
headers: {'Content-Type': 'application/json'},
body: json.encode({'token': token}),
);
@ -45,10 +41,7 @@ class _CreateMatchPageState extends State<CreateMatchPage> {
if (response.statusCode == 200) {
final data = json.decode(response.body);
setState(() {
_matchId = _isTwoPlayerModeEnabled
? data['match_id'].toString() +
'D' // Append "D" for two-player mode
: data['match_id'].toString();
_matchId = data['match_id'].toString();
});
_showToast('Match created successfully!');
} else {
@ -63,6 +56,7 @@ class _CreateMatchPageState extends State<CreateMatchPage> {
}
}
// Show a Toast message (SnackBar in Flutter)
void _showToast(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
@ -72,6 +66,9 @@ class _CreateMatchPageState extends State<CreateMatchPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Create Match'),
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
@ -91,27 +88,11 @@ class _CreateMatchPageState extends State<CreateMatchPage> {
textAlign: TextAlign.center,
),
SizedBox(height: 16),
Text(
'Due to current limitations in how we handle matchmaking, only the joining player can control the match. This is only a temporary solution to a problem we are actively fixing.',
style: TextStyle(fontSize: 14),
textAlign: TextAlign.center,
),
SizedBox(height: 16),
_isLoading
? CircularProgressIndicator() // Show loading spinner
: ElevatedButton(
onPressed: _createMatch,
child: Text('Create Match'),
),
SizedBox(height: 16),
SwitchListTile(
title: Text('Enable 2 player mode'),
value: _isTwoPlayerModeEnabled,
onChanged: (bool value) {
setState(() {
_isTwoPlayerModeEnabled = value;
});
},
onPressed: _createMatch,
child: Text('Create Match'),
),
SizedBox(height: 16),
if (_matchId != null)

View file

@ -2,7 +2,6 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import '../../globals.dart';
class AddFriendPage extends StatefulWidget {
@override
@ -14,8 +13,8 @@ class _AddFriendPageState extends State<AddFriendPage> {
List<dynamic> _friends = [];
bool _isLoading = false;
final String _addFriendApiUrl = '$apiurl/add_friend';
final String _getFriendsApiUrl = '$apiurl/get_friends';
final String _addFriendApiUrl = 'http://api.dthpp.mercurio.moe/add_friend';
final String _getFriendsApiUrl = 'http://api.dthpp.mercurio.moe/get_friends';
// Method to add a friend
Future<void> _addFriend(String friendUid) async {
@ -125,17 +124,17 @@ class _AddFriendPageState extends State<AddFriendPage> {
_isLoading
? Center(child: CircularProgressIndicator())
: Expanded(
child: ListView.builder(
itemCount: _friends.length,
itemBuilder: (context, index) {
final friend = _friends[index];
return ListTile(
title: Text(friend['name']),
subtitle: Text('UID: ${friend['uid']}'),
);
},
),
),
child: ListView.builder(
itemCount: _friends.length,
itemBuilder: (context, index) {
final friend = _friends[index];
return ListTile(
title: Text(friend['name']),
subtitle: Text('UID: ${friend['uid']}'),
);
},
),
),
],
),
),

View file

@ -2,7 +2,6 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import '../../globals.dart';
class JoinMatchPage extends StatefulWidget {
@override
@ -13,23 +12,14 @@ class _JoinMatchPageState extends State<JoinMatchPage> {
TextEditingController _matchIdController = TextEditingController();
bool _isJoined = false;
bool _isLoading = false;
bool _is2v2Mode = false;
int _selectedSlot = 2;
int _player1Score = 0;
int _player2Score = 0;
int _player3Score = 0;
int _player4Score = 0;
String? _matchId;
String? _player1name;
String? _player2name;
List<String> _players = [];
bool _canEdit = false;
final String _joinMatchApiUrl = '$apiurl/joinmatch';
final String _joinMatch2v2ApiUrl = '$apiurl/joinmatch_2v2';
final String _endMatchApiUrl = '$apiurl/endmatch';
final String _endFourApiUrl = '$apiurl/endfour';
final String _joinMatchApiUrl = 'http://api.dthpp.mercurio.moe/joinmatch'; // Replace with your API endpoint
final String _endMatchApiUrl = 'http://api.dthpp.mercurio.moe/endmatch'; // Replace with your API endpoint
// Join Match Function
Future<void> _joinMatch() async {
setState(() {
_isLoading = true;
@ -48,60 +38,22 @@ class _JoinMatchPageState extends State<JoinMatchPage> {
}
try {
if (_is2v2Mode) {
// Joining a 2v2 match
final response = await http.post(
Uri.parse(_joinMatch2v2ApiUrl),
headers: {'Content-Type': 'application/json'},
body: json.encode({
'token': token,
'match_id': int.parse(matchId),
'slot': _selectedSlot,
}),
);
final response = await http.post(
Uri.parse(_joinMatchApiUrl),
headers: {'Content-Type': 'application/json'},
body: json.encode({'token': token, 'match_id': int.parse(matchId)}),
);
if (response.statusCode == 200) {
final responseData = json.decode(response.body);
setState(() {
_isJoined = true;
_matchId = matchId;
_players = List<String>.from(
responseData['players'].map((p) => p['name']));
_canEdit = responseData['canEdit'];
_player1Score = 0;
_player2Score = 0;
_player3Score = 0;
_player4Score = 0;
});
_showToast('Joined match successfully!');
} else {
_showToast('Failed to join 2v2 match.');
}
if (response.statusCode == 200) {
setState(() {
_isJoined = true;
_player1Score = 0;
_player2Score = 0;
_matchId = matchId;
});
_showToast('Joined match successfully!');
} else {
// Joining a 1v1 match
final response = await http.post(
Uri.parse(_joinMatchApiUrl),
headers: {'Content-Type': 'application/json'},
body: json.encode({
'token': token,
'match_id': int.parse(matchId),
}),
);
if (response.statusCode == 200) {
final responseData = json.decode(response.body);
setState(() {
_isJoined = true;
_player1name = responseData['player1_name'];
_player2name = responseData['player2_name'];
_player1Score = 0;
_player2Score = 0;
_matchId = matchId;
});
_showToast('Joined match successfully!');
} else {
_showToast('Failed to join match.');
}
_showToast('Failed to join match.');
}
} catch (e) {
_showToast('Error: $e');
@ -112,6 +64,18 @@ class _JoinMatchPageState extends State<JoinMatchPage> {
}
}
// Increment/Decrement Player Scores
void _updateScore(int player, int delta) {
setState(() {
if (player == 1) {
_player1Score += delta;
} else if (player == 2) {
_player2Score += delta;
}
});
}
// End Match Function
Future<void> _endMatch() async {
setState(() {
_isLoading = true;
@ -129,42 +93,21 @@ class _JoinMatchPageState extends State<JoinMatchPage> {
}
try {
if (_is2v2Mode) {
// End the 2v2 match using the /endfour API
final response = await http.post(
Uri.parse(_endFourApiUrl),
headers: {'Content-Type': 'application/json'},
body: json.encode({
'match_id': int.parse(_matchId!),
'player1_team1_score': _player1Score,
'player2_team1_score': _player2Score,
'player1_team2_score': _player3Score,
'player2_team2_score': _player4Score,
}),
);
final response = await http.post(
Uri.parse(_endMatchApiUrl),
headers: {'Content-Type': 'application/json'},
body: json.encode({
'match_id': int.parse(_matchId!),
'player1_score': _player1Score,
'player2_score': _player2Score,
}),
);
if (response.statusCode == 200) {
_showToast('2v2 match ended successfully!');
} else {
_showToast('Failed to end 2v2 match.');
}
if (response.statusCode == 200) {
_showToast('Match ended successfully!');
Navigator.pop(context);
} else {
// End the 1v1 match
final response = await http.post(
Uri.parse(_endMatchApiUrl),
headers: {'Content-Type': 'application/json'},
body: json.encode({
'match_id': int.parse(_matchId!),
'player1_score': _player1Score,
'player2_score': _player2Score,
}),
);
if (response.statusCode == 200) {
_showToast('Match ended successfully!');
} else {
_showToast('Failed to end match.');
}
_showToast('Failed to end match.');
}
} catch (e) {
_showToast('Error: $e');
@ -184,313 +127,89 @@ class _JoinMatchPageState extends State<JoinMatchPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Join Match'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: _isLoading
? Center(child: CircularProgressIndicator())
: _isJoined
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Display for 2v2 match
if (_is2v2Mode && _canEdit) ...[
Text('2v2 Match',
style:
TextStyle(color: Colors.white, fontSize: 24)),
SizedBox(height: 16),
// First Team
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
children: [
Text(_players[0],
style: TextStyle(color: Colors.white)),
Row(
children: [
IconButton(
icon: Icon(Icons.remove,
color: Colors.white),
onPressed: () => _updateScore(1, -1)),
Container(
width: 70,
height: 50,
decoration: BoxDecoration(
border: Border.all(
color: Colors.white,
width: 2),
borderRadius:
BorderRadius.circular(8)),
child: Center(
child: Text('$_player1Score',
style: TextStyle(
color: Colors.white,
fontWeight:
FontWeight.bold,
fontSize: 24)))),
IconButton(
icon: Icon(Icons.add,
color: Colors.white),
onPressed: () => _updateScore(1, 1)),
],
),
],
),
SizedBox(width: 20),
Column(
children: [
Text(_players[1],
style: TextStyle(color: Colors.white)),
Row(
children: [
IconButton(
icon: Icon(Icons.remove,
color: Colors.white),
onPressed: () => _updateScore(2, -1)),
Container(
width: 70,
height: 50,
decoration: BoxDecoration(
border: Border.all(
color: Colors.white,
width: 2),
borderRadius:
BorderRadius.circular(8)),
child: Center(
child: Text('$_player2Score',
style: TextStyle(
color: Colors.white,
fontWeight:
FontWeight.bold,
fontSize: 24)))),
IconButton(
icon: Icon(Icons.add,
color: Colors.white),
onPressed: () => _updateScore(2, 1)),
],
),
],
),
],
),
SizedBox(height: 32),
// Second Team
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
children: [
Text(_players[2],
style: TextStyle(color: Colors.white)),
Row(
children: [
IconButton(
icon: Icon(Icons.remove,
color: Colors.white),
onPressed: () => _updateScore(3, -1)),
Container(
width: 70,
height: 50,
decoration: BoxDecoration(
border: Border.all(
color: Colors.white,
width: 2),
borderRadius:
BorderRadius.circular(8)),
child: Center(
child: Text('$_player3Score',
style: TextStyle(
color: Colors.white,
fontWeight:
FontWeight.bold,
fontSize: 24)))),
IconButton(
icon: Icon(Icons.add,
color: Colors.white),
onPressed: () => _updateScore(3, 1)),
],
),
],
),
SizedBox(width: 20),
Column(
children: [
Text(_players[3],
style: TextStyle(color: Colors.white)),
Row(
children: [
IconButton(
icon: Icon(Icons.remove,
color: Colors.white),
onPressed: () => _updateScore(4, -1)),
Container(
width: 75,
height: 50,
decoration: BoxDecoration(
border: Border.all(
color: Colors.white,
width: 2),
borderRadius:
BorderRadius.circular(8)),
child: Center(
child: Text('$_player4Score',
style: TextStyle(
color: Colors.white,
fontWeight:
FontWeight.bold,
fontSize: 24)))),
IconButton(
icon: Icon(Icons.add,
color: Colors.white),
onPressed: () => _updateScore(4, 1)),
],
),
],
),
],
),
],
// 1v1 Match UI
if (!_is2v2Mode) ...[
Text(_player1name ?? 'Player 1',
style: TextStyle(color: Colors.white)),
SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(Icons.remove, color: Colors.white),
onPressed: () => _updateScore(1, -1)),
Container(
width: 100,
height: 50,
decoration: BoxDecoration(
border: Border.all(
color: Colors.white, width: 2),
borderRadius: BorderRadius.circular(8)),
child: Center(
child: Text('$_player1Score',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 24)))),
IconButton(
icon: Icon(Icons.add, color: Colors.white),
onPressed: () => _updateScore(1, 1)),
],
),
SizedBox(height: 8),
Divider(
color: Colors.white,
thickness: 2,
indent: 80,
endIndent: 80),
SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(Icons.remove, color: Colors.white),
onPressed: () => _updateScore(2, -1)),
Container(
width: 100,
height: 50,
decoration: BoxDecoration(
border: Border.all(
color: Colors.white, width: 2),
borderRadius: BorderRadius.circular(8)),
child: Center(
child: Text('$_player2Score',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 24)))),
IconButton(
icon: Icon(Icons.add, color: Colors.white),
onPressed: () => _updateScore(2, 1)),
],
),
SizedBox(height: 8),
Text(_player2name ?? 'Player 2',
style: TextStyle(color: Colors.white)),
],
SizedBox(height: 32),
ElevatedButton(
onPressed: _endMatch,
child: Text('End Match'),
),
],
),
)
: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Match ID Text Field
TextField(
controller: _matchIdController,
decoration: InputDecoration(
labelText: 'Enter Match ID',
border: OutlineInputBorder(),
),
),
SizedBox(height: 16),
// Toggle for 2v2 Mode
SwitchListTile(
title: Text('Enable 2v2 Mode'),
value: _is2v2Mode,
onChanged: (value) {
setState(() {
_is2v2Mode = value;
});
},
),
// If 2v2 is selected, display slot selection
if (_is2v2Mode) ...[
DropdownButton<int>(
value: _selectedSlot,
items: [2, 3, 4].map((int value) {
return DropdownMenuItem<int>(
value: value,
child: Text('Slot $value'),
);
}).toList(),
onChanged: (int? newValue) {
setState(() {
_selectedSlot = newValue!;
});
},
),
],
SizedBox(height: 16),
ElevatedButton(
onPressed: () {
if (_matchIdController.text.isNotEmpty) {
_joinMatch();
} else {
_showToast('Please enter a Match ID.');
}
},
child: Text('Join Match'),
),
],
),
? Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(Icons.remove),
onPressed: () => _updateScore(1, -1),
),
Text(
'Player 1 Score: $_player1Score',
style: TextStyle(fontSize: 20),
),
IconButton(
icon: Icon(Icons.add),
onPressed: () => _updateScore(1, 1),
),
],
),
SizedBox(height: 16),
// Player 2 Score Controls
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(Icons.remove),
onPressed: () => _updateScore(2, -1),
),
Text(
'Player 2 Score: $_player2Score',
style: TextStyle(fontSize: 20),
),
IconButton(
icon: Icon(Icons.add),
onPressed: () => _updateScore(2, 1),
),
],
),
SizedBox(height: 32),
// End Match Button
ElevatedButton(
onPressed: _endMatch,
child: Text('End Match'),
),
],
)
: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Match ID Text Field
TextField(
controller: _matchIdController,
decoration: InputDecoration(
labelText: 'Enter Match ID',
border: OutlineInputBorder(),
),
),
SizedBox(height: 16),
// Join Match Button
ElevatedButton(
onPressed: () {
if (_matchIdController.text.isNotEmpty) {
_joinMatch();
} else {
_showToast('Please enter a Match ID.');
}
},
child: Text('Join Match'),
),
],
),
),
);
}
void _updateScore(int playerIndex, int increment) {
setState(() {
if (playerIndex == 1) {
_player1Score += increment;
} else if (playerIndex == 2) {
_player2Score += increment;
} else if (playerIndex == 3) {
_player3Score += increment;
} else if (playerIndex == 4) {
_player4Score += increment;
}
});
}
}

View file

@ -1,7 +1,6 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import '../../globals.dart';
class LeaderboardPage extends StatefulWidget {
@override
@ -11,9 +10,8 @@ class LeaderboardPage extends StatefulWidget {
class _LeaderboardPageState extends State<LeaderboardPage> {
List<dynamic> _leaderboard = [];
bool _isLoading = true;
bool _sortByOskMu = false;
final String _leaderboardApi = '$apiurl/leaderboards';
final String _apiUrl = 'http://api.dthpp.mercurio.moe/leaderboards';
Future<void> _fetchLeaderboard() async {
setState(() {
@ -21,13 +19,12 @@ class _LeaderboardPageState extends State<LeaderboardPage> {
});
try {
final response = await http.get(Uri.parse(_leaderboardApi));
final response = await http.get(Uri.parse(_apiUrl));
if (response.statusCode == 200) {
List<dynamic> data = json.decode(response.body);
setState(() {
_leaderboard = data;
_sortLeaderboard();
_isLoading = false;
});
} else {
@ -44,23 +41,6 @@ class _LeaderboardPageState extends State<LeaderboardPage> {
}
}
void _sortLeaderboard() {
setState(() {
if (_sortByOskMu) {
_leaderboard.sort((a, b) => b['osk_mu'].compareTo(a['osk_mu']));
} else {
_leaderboard.sort((a, b) => b['elo_rating'].compareTo(a['elo_rating']));
}
});
}
void _toggleSort() {
setState(() {
_sortByOskMu = !_sortByOskMu;
_sortLeaderboard();
});
}
void _showError(String message) {
showDialog(
context: context,
@ -82,58 +62,35 @@ class _LeaderboardPageState extends State<LeaderboardPage> {
@override
void initState() {
super.initState();
_fetchLeaderboard();
_fetchLeaderboard();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Leaderboard'),
actions: [
IconButton(
icon: Icon(Icons.sort),
onPressed: _toggleSort,
tooltip: _sortByOskMu ? 'Sort by Elo' : 'Sort by Osk_Mu',
),
],
),
body: _isLoading
? Center(child: CircularProgressIndicator())
? Center(child: CircularProgressIndicator())
: RefreshIndicator(
onRefresh: _fetchLeaderboard,
child: ListView.builder(
itemCount: _leaderboard.length,
itemBuilder: (context, index) {
var player = _leaderboard[index];
String truncatedOskMu = player['osk_mu'].toStringAsFixed(3);
String assetName = _sortByOskMu
? 'assets/player_${index}_dp.png'
: 'assets/player_${index}.png';
return Card(
margin: EdgeInsets.all(8),
child: ListTile(
contentPadding: EdgeInsets.all(10),
leading: CircleAvatar(
child: Text(player['player_name'][0].toUpperCase())),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(player['player_name']),
if (index < 3)
Image.asset(assetName, width: 45, height: 45),
],
),
subtitle: Text(
'Elo: ${player['elo_rating']} | TSC: $truncatedOskMu'),
trailing: Text('UID: ${player['friend_code']}'),
),
);
},
onRefresh: _fetchLeaderboard,
child: ListView.builder(
itemCount: _leaderboard.length,
itemBuilder: (context, index) {
var player = _leaderboard[index];
return Card(
margin: EdgeInsets.all(8),
child: ListTile(
contentPadding: EdgeInsets.all(10),
leading: CircleAvatar(
child: Text(player['player_name'][0].toUpperCase()),
),
title: Text(player['player_name']),
subtitle: Text('Elo Rating: ${player['elo_rating']}'),
trailing: Text('Friend Code: ${player['friend_code']}'),
),
),
);
},
),
),
);
}
}

View file

@ -3,7 +3,6 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import '../../globals.dart';
class ProfilePage extends StatefulWidget {
@override
@ -15,11 +14,9 @@ class _ProfilePageState extends State<ProfilePage> {
String? _name;
int? _uid;
int? _elo;
double? _mu;
double? _unc;
List<dynamic> _matches = [];
final String _getProfileApiUrl = '$apiurl/getprofile';
final String _getProfileApiUrl = 'http://api.dthpp.mercurio.moe/getprofile';
@override
void initState() {
@ -49,8 +46,6 @@ class _ProfilePageState extends State<ProfilePage> {
_name = data['name'];
_uid = data['uid'];
_elo = data['elo'];
_mu = data['osk_mu'];
_unc = data['osk_sig'];
_matches = data['matches'];
_isLoading = false;
});
@ -78,222 +73,120 @@ class _ProfilePageState extends State<ProfilePage> {
);
}
String getRankImage(int? elo) {
if (elo == null || elo < -100) return 'assets/none.png';
if (elo >= 120) return 'assets/infdan.png';
if (elo >= 90) return 'assets/SS.png';
if (elo >= 60) return 'assets/S.png';
if (elo >= 30) return 'assets/A.png';
if (elo >= 0) return 'assets/B.png';
if (elo >= -30) return 'assets/C.png';
if (elo >= -60) return 'assets/D.png';
return 'assets/E.png';
}
void _showExplanationDialog() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('ELO, OSK, and UNC Explanation'),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
// ELO Section
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"ELO (Elo Rating):",
style: TextStyle(fontWeight: FontWeight.bold),
),
SizedBox(height: 4.0),
Text(
"ELO is a widely-used rating system designed to measure the relative skill levels of players in two-player games. It was my initial pick for a testing environment since I only really thought about 1v1 matches, and because it had a readily available Python implementation. I'm lazy.",
),
SizedBox(height: 8.0),
],
),
// OSK Section
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"OSK (OpenSkill Mu):",
style: TextStyle(fontWeight: FontWeight.bold),
),
SizedBox(height: 4.0),
Text(
"OSKmu is a skill rating system based on the OpenSkill model, which is a probabilistic framework for estimating a player's skill. Unlike ELO, which is purely a point-based system, OpenSkill Mu takes into account not just the outcome of matches but also the degree of uncertainty in a player's skill estimation. Since I set up the system to have a 0-base-elo, I had to adapt the OpenSkill implementation to a standard 25 OSK and 8.33 uncertainty.",
),
SizedBox(height: 8.0),
],
),
// UNC Section
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Uncertainty (UNC):",
style: TextStyle(fontWeight: FontWeight.bold),
),
SizedBox(height: 4.0),
Text(
"This is a measure of how confident the system is about a player's skill rating. A higher uncertainty value means the system is less confident about the accuracy of the player's skill estimation, while a lower uncertainty indicates more confidence in the player's rating.",
),
],
),
],
),
),
actions: <Widget>[
TextButton(
child: Text('Close'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: _isLoading
? Center(child: CircularProgressIndicator())
: RefreshIndicator(
onRefresh: _fetchProfileData,
child: Column(
onRefresh: _fetchProfileData,
child: Column(
children: [
// Profile Details
Container(
padding: EdgeInsets.all(16.0),
child: Row(
children: [
// Profile Details
Container(
padding: EdgeInsets.all(16.0),
child: Row(
children: [
CircleAvatar(
backgroundColor: _generateRandomColor(),
child: Text(
_name != null && _name!.isNotEmpty
? _name![0].toUpperCase()
: '?',
style: TextStyle(fontSize: 24, color: Colors.white),
),
radius: 40,
),
SizedBox(width: 16),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_name ?? 'Name not available',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 6),
Row(
children: [
Text('UID: ${_uid ?? 'N/A'}'),
SizedBox(
width: 30,
),
Image.asset(
getRankImage(_elo),
width: 100,
height: 30,
),
],
),
Row(
children: [
Text(
'ELO: ${_elo ?? 'N/A'} | OSK: ${_mu != null ? _mu?.toStringAsFixed(3) : 'N/A'} | UNC: ${_unc != null ? _unc?.toStringAsFixed(3) : 'N/A'}'),
IconButton(
icon: Icon(Icons.help_outline),
onPressed: _showExplanationDialog,
tooltip: 'What are ELO, OSK, and UNC?',
),
],
),
],
),
],
// Profile Icon
CircleAvatar(
backgroundColor: _generateRandomColor(),
child: Text(
_name != null && _name!.isNotEmpty
? _name![0].toUpperCase()
: '?',
style: TextStyle(fontSize: 24, color: Colors.white),
),
radius: 40,
),
// Recent Matches
Expanded(
child: _matches.isEmpty
? Center(
child: Text(
"You haven't played any matches yet",
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
)
: ListView.builder(
itemCount: _matches.length,
itemBuilder: (context, index) {
final match = _matches[index];
final result = match['result'];
final eloChange = match['elo_change'];
return Card(
margin: EdgeInsets.symmetric(
horizontal: 16.0, vertical: 8.0),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
'Match ID: ${match['match_id']}',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
Text(
'Opponent: ${match['opponent_name']}'),
Row(
children: [
Text(
'$result',
style: TextStyle(
fontWeight: FontWeight.bold,
color: result == 'Win'
? Colors.green
: Colors.red,
),
),
SizedBox(width: 16),
if (eloChange != null)
Text(
'ELO Change: ${eloChange > 0 ? '+' : ''}$eloChange',
style: TextStyle(
color: eloChange > 0
? Colors.green
: Colors.red,
),
),
],
),
],
),
),
);
},
),
SizedBox(width: 16),
// Profile Info
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_name ?? 'Name not available',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
Text('UID: ${_uid ?? 'N/A'}'),
Text('ELO: ${_elo ?? 'N/A'}'),
],
),
],
),
),
SizedBox(height: 16),
// Recent Matches
Expanded(
child: _matches.isEmpty
? Center(
child: Text(
"You haven't played any matches yet",
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
)
: ListView.builder(
itemCount: _matches.length,
itemBuilder: (context, index) {
final match = _matches[index];
final result = match['result'];
final eloChange = match['elo_change'];
return Card(
margin: EdgeInsets.symmetric(
horizontal: 16.0, vertical: 8.0),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Match ID: ${match['match_id']}',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
Text('Opponent: ${match['opponent_name']}'),
Row(
children: [
Text(
'$result',
style: TextStyle(
fontWeight: FontWeight.bold,
color: result == 'Win'
? Colors.green
: Colors.red,
),
),
SizedBox(width: 16),
if (eloChange != null)
Text(
'ELO Change: ${eloChange > 0 ? '+' : ''}$eloChange',
style: TextStyle(
color: eloChange > 0
? Colors.green
: Colors.red,
),
),
],
),
],
),
),
);
},
),
),
],
),
),
);
}
}

View file

@ -1,2 +1 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"

View file

@ -1,2 +1 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"

View file

@ -5,10 +5,8 @@
import FlutterMacOS
import Foundation
import package_info
import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FLTPackageInfoPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
}

View file

@ -1,43 +0,0 @@
platform :osx, '10.14'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_macos_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_macos_build_settings(target)
end
end

View file

@ -1,8 +1,8 @@
name: pingpongapp
description: "DTH Ping Pong Score tracking app"
description: "A new Flutter project."
publish_to: 'none'
version: 0.0.57+1
version: 0.0.32+1
environment:
sdk: '>=3.4.3 <4.0.0'
@ -14,10 +14,6 @@ dependencies:
shared_preferences: ^2.3.3
http: ^1.2.2
logger: ^2.5.0
package_info: ^2.0.2
font_awesome_flutter: ^10.8.0
url_launcher: ^6.3.1
fl_chart: ^0.70.2
dev_dependencies:
flutter_test:
@ -27,8 +23,9 @@ flutter:
uses-material-design: true
# To add assets to your application, add an assets section, like this:
assets:
- assets/
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware