This commit is contained in:
2025-11-24 17:53:00 +09:00
parent e992a5ca5e
commit dde81cab65
34 changed files with 3718 additions and 469 deletions
+13 -6
View File
@@ -7,16 +7,20 @@ plugins {
android {
namespace = "com.playwith.playwith_app"
compileSdk = flutter.compileSdkVersion
compileSdk = 34
ndkVersion = flutter.ndkVersion
// [수정할 부분]
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
// 기존 1.8 -> 17로 변경
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
// [수정할 부분]
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
// 기존 '1.8' -> '17'로 변경
jvmTarget = "17"
}
defaultConfig {
@@ -24,8 +28,11 @@ android {
applicationId = "com.playwith.playwith_app"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
// [수정] 최소 SDK는 21 이상이면 됨
minSdk = flutter.minSdkVersion
// [수정] 타겟 SDK도 34로 올림
targetSdk = 34
versionCode = flutter.versionCode
versionName = flutter.versionName
}
@@ -1,4 +1,20 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CAMERA"/>
<application
android:label="playwith_app"
android:name="${applicationName}"
@@ -30,6 +46,12 @@
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-9504446465764716~3452126047"
/>
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
@@ -1,5 +1,43 @@
package com.playwith.playwith_app
import android.content.Context
import android.net.wifi.WifiManager
import android.os.Bundle
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()
class MainActivity: FlutterActivity() {
// 멀티캐스트 잠금 객체
private var multicastLock: WifiManager.MulticastLock? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
acquireMulticastLock()
}
override fun onDestroy() {
super.onDestroy()
releaseMulticastLock()
}
private fun acquireMulticastLock() {
try {
val wifiManager = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
// "multicastLock" 태그로 잠금 생성
multicastLock = wifiManager.createMulticastLock("multicastLock")
multicastLock?.setReferenceCounted(true)
multicastLock?.acquire() // 잠금 활성화 (멀티캐스트 수신 허용)
println("[Android Native] Multicast Lock Acquired!")
} catch (e: Exception) {
println("[Android Native] Failed to acquire Multicast Lock: $e")
}
}
private fun releaseMulticastLock() {
try {
multicastLock?.release() // 잠금 해제
println("[Android Native] Multicast Lock Released!")
} catch (e: Exception) {
println("[Android Native] Failed to release Multicast Lock: $e")
}
}
}
+5 -5
View File
@@ -5,20 +5,20 @@ allprojects {
}
}
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
.get()
// [핵심] 빌드 결과물 위치를 Flutter 표준 경로(../../build)로 변경
// 이 코드가 없으면 Flutter가 APK를 찾지 못해 에러가 납니다.
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}
}
+213
View File
@@ -0,0 +1,213 @@
PODS:
- audioplayers_darwin (0.0.1):
- Flutter
- FlutterMacOS
- bonsoir_darwin (0.0.1):
- Flutter
- FlutterMacOS
- DKImagePickerController/Core (4.3.9):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
- DKImagePickerController/ImageDataManager (4.3.9)
- DKImagePickerController/PhotoGallery (4.3.9):
- DKImagePickerController/Core
- DKPhotoGallery
- DKImagePickerController/Resource (4.3.9)
- DKPhotoGallery (0.0.19):
- DKPhotoGallery/Core (= 0.0.19)
- DKPhotoGallery/Model (= 0.0.19)
- DKPhotoGallery/Preview (= 0.0.19)
- DKPhotoGallery/Resource (= 0.0.19)
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Core (0.0.19):
- DKPhotoGallery/Model
- DKPhotoGallery/Preview
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Model (0.0.19):
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Preview (0.0.19):
- DKPhotoGallery/Model
- DKPhotoGallery/Resource
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Resource (0.0.19):
- SDWebImage
- SwiftyGif
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
- Flutter (1.0.0)
- GoogleDataTransport (9.4.1):
- GoogleUtilities/Environment (~> 7.7)
- nanopb (< 2.30911.0, >= 2.30908.0)
- PromisesObjC (< 3.0, >= 1.2)
- GoogleMLKit/BarcodeScanning (6.0.0):
- GoogleMLKit/MLKitCore
- MLKitBarcodeScanning (~> 5.0.0)
- GoogleMLKit/MLKitCore (6.0.0):
- MLKitCommon (~> 11.0.0)
- GoogleToolboxForMac/Defines (4.2.1)
- GoogleToolboxForMac/Logger (4.2.1):
- GoogleToolboxForMac/Defines (= 4.2.1)
- "GoogleToolboxForMac/NSData+zlib (4.2.1)":
- GoogleToolboxForMac/Defines (= 4.2.1)
- GoogleUtilities/Environment (7.13.3):
- GoogleUtilities/Privacy
- PromisesObjC (< 3.0, >= 1.2)
- GoogleUtilities/Logger (7.13.3):
- GoogleUtilities/Environment
- GoogleUtilities/Privacy
- GoogleUtilities/Privacy (7.13.3)
- GoogleUtilities/UserDefaults (7.13.3):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GoogleUtilitiesComponents (1.1.0):
- GoogleUtilities/Logger
- GTMSessionFetcher/Core (3.5.0)
- image_picker_ios (0.0.1):
- Flutter
- MLImage (1.0.0-beta5)
- MLKitBarcodeScanning (5.0.0):
- MLKitCommon (~> 11.0)
- MLKitVision (~> 7.0)
- MLKitCommon (11.0.0):
- GoogleDataTransport (< 10.0, >= 9.4.1)
- GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1)
- "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)"
- GoogleUtilities/UserDefaults (< 8.0, >= 7.13.0)
- GoogleUtilitiesComponents (~> 1.0)
- GTMSessionFetcher/Core (< 4.0, >= 3.3.2)
- MLKitVision (7.0.0):
- GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1)
- "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)"
- GTMSessionFetcher/Core (< 4.0, >= 3.3.2)
- MLImage (= 1.0.0-beta5)
- MLKitCommon (~> 11.0)
- mobile_scanner (5.2.3):
- Flutter
- GoogleMLKit/BarcodeScanning (~> 6.0.0)
- nanopb (2.30910.0):
- nanopb/decode (= 2.30910.0)
- nanopb/encode (= 2.30910.0)
- nanopb/decode (2.30910.0)
- nanopb/encode (2.30910.0)
- objective_c (0.0.1):
- Flutter
- permission_handler_apple (9.3.0):
- Flutter
- PromisesObjC (2.4.0)
- SDWebImage (5.21.1):
- SDWebImage/Core (= 5.21.1)
- SDWebImage/Core (5.21.1)
- sqlite3 (3.50.4):
- sqlite3/common (= 3.50.4)
- sqlite3/common (3.50.4)
- sqlite3/dbstatvtab (3.50.4):
- sqlite3/common
- sqlite3/fts5 (3.50.4):
- sqlite3/common
- sqlite3/math (3.50.4):
- sqlite3/common
- sqlite3/perf-threadsafe (3.50.4):
- sqlite3/common
- sqlite3/rtree (3.50.4):
- sqlite3/common
- sqlite3/session (3.50.4):
- sqlite3/common
- sqlite3_flutter_libs (0.0.1):
- Flutter
- FlutterMacOS
- sqlite3 (~> 3.50.4)
- sqlite3/dbstatvtab
- sqlite3/fts5
- sqlite3/math
- sqlite3/perf-threadsafe
- sqlite3/rtree
- sqlite3/session
- SwiftyGif (5.4.5)
DEPENDENCIES:
- audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/darwin`)
- bonsoir_darwin (from `.symlinks/plugins/bonsoir_darwin/darwin`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/ios`)
- objective_c (from `.symlinks/plugins/objective_c/ios`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
SPEC REPOS:
trunk:
- DKImagePickerController
- DKPhotoGallery
- GoogleDataTransport
- GoogleMLKit
- GoogleToolboxForMac
- GoogleUtilities
- GoogleUtilitiesComponents
- GTMSessionFetcher
- MLImage
- MLKitBarcodeScanning
- MLKitCommon
- MLKitVision
- nanopb
- PromisesObjC
- SDWebImage
- sqlite3
- SwiftyGif
EXTERNAL SOURCES:
audioplayers_darwin:
:path: ".symlinks/plugins/audioplayers_darwin/darwin"
bonsoir_darwin:
:path: ".symlinks/plugins/bonsoir_darwin/darwin"
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
Flutter:
:path: Flutter
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
mobile_scanner:
:path: ".symlinks/plugins/mobile_scanner/ios"
objective_c:
:path: ".symlinks/plugins/objective_c/ios"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
sqlite3_flutter_libs:
:path: ".symlinks/plugins/sqlite3_flutter_libs/darwin"
SPEC CHECKSUMS:
audioplayers_darwin: 4027b33a8f471d996c13f71cb77f0b1583b5d923
bonsoir_darwin: e3b8526c42ca46a885142df84229131dfabea842
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a
GoogleMLKit: 97ac7af399057e99182ee8edfa8249e3226a4065
GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8
GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15
GoogleUtilitiesComponents: 679b2c881db3b615a2777504623df6122dd20afe
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b
MLImage: 1824212150da33ef225fbd3dc49f184cf611046c
MLKitBarcodeScanning: 10ca0845a6d15f2f6e911f682a1998b68b973e8b
MLKitCommon: afec63980417d29ffbb4790529a1b0a2291699e1
MLKitVision: e858c5f125ecc288e4a31127928301eaba9ae0c1
mobile_scanner: 96e91f2e1fb396bb7df8da40429ba8dfad664740
nanopb: 438bc412db1928dac798aa6fd75726007be04262
objective_c: 77e887b5ba1827970907e10e832eec1683f3431d
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
SDWebImage: f29024626962457f3470184232766516dee8dfea
sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b
sqlite3_flutter_libs: 86f82662868ee26ff3451f73cac9c5fc2a1f57fa
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
COCOAPODS: 1.16.2
@@ -10,6 +10,8 @@
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 */; };
51DBF7C9342CCC1E84264FAF /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57CE06CA7580196D1806764B /* Pods_RunnerTests.framework */; };
576F22A04C2FA735A59CCB8C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8D742755804ABA6F6EFA09B0 /* Pods_Runner.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
@@ -44,10 +46,16 @@
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; 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; };
34B441CD4D8D7C51D92293E3 /* 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>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
4E75157E0476F57C9A36AAFF /* 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>"; };
57CE06CA7580196D1806764B /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
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>"; };
7CAA68E8ECB59955A38AF698 /* 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>"; };
7DB0FB06F75F1BDF46E6C95B /* 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>"; };
8D742755804ABA6F6EFA09B0 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
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; };
@@ -55,13 +63,24 @@
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>"; };
D4D50D0F4B952368E1319826 /* 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>"; };
F6B684F378DF7FE6F2C3CABC /* 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>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
373154A272D279F7BAD56A3D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
51DBF7C9342CCC1E84264FAF /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
576F22A04C2FA735A59CCB8C /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -76,6 +95,15 @@
path = RunnerTests;
sourceTree = "<group>";
};
86E8FF9E84BDE142E9B2A7E5 /* Frameworks */ = {
isa = PBXGroup;
children = (
8D742755804ABA6F6EFA09B0 /* Pods_Runner.framework */,
57CE06CA7580196D1806764B /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
@@ -94,6 +122,8 @@
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
A70DA976E0448863AE3AAB92 /* Pods */,
86E8FF9E84BDE142E9B2A7E5 /* Frameworks */,
);
sourceTree = "<group>";
};
@@ -121,6 +151,20 @@
path = Runner;
sourceTree = "<group>";
};
A70DA976E0448863AE3AAB92 /* Pods */ = {
isa = PBXGroup;
children = (
34B441CD4D8D7C51D92293E3 /* Pods-Runner.debug.xcconfig */,
F6B684F378DF7FE6F2C3CABC /* Pods-Runner.release.xcconfig */,
D4D50D0F4B952368E1319826 /* Pods-Runner.profile.xcconfig */,
7DB0FB06F75F1BDF46E6C95B /* Pods-RunnerTests.debug.xcconfig */,
4E75157E0476F57C9A36AAFF /* Pods-RunnerTests.release.xcconfig */,
7CAA68E8ECB59955A38AF698 /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -128,8 +172,10 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
72139F7C55C8F7C501C7B164 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
373154A272D279F7BAD56A3D /* Frameworks */,
);
buildRules = (
);
@@ -145,12 +191,15 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
88859AD75AFEC32170AD0FCA /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
9DCF60722691A8C657DDBEC4 /* [CP] Embed Pods Frameworks */,
AB0501777866B4369253924C /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -238,6 +287,50 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
72139F7C55C8F7C501C7B164 /* [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;
};
88859AD75AFEC32170AD0FCA /* [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;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@@ -253,6 +346,40 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
9DCF60722691A8C657DDBEC4 /* [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;
};
AB0501777866B4369253924C /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -379,6 +506,7 @@
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7DB0FB06F75F1BDF46E6C95B /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -396,6 +524,7 @@
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 4E75157E0476F57C9A36AAFF /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -411,6 +540,7 @@
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7CAA68E8ECB59955A38AF698 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -4,4 +4,7 @@
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
+18
View File
@@ -45,5 +45,23 @@
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>NSLocalNetworkUsageDescription</key>
<string>주변 친구들과 게임을 하기 위해 로컬 네트워크 권한이 필요합니다.</string>
<key>NSBonjourServices</key>
<array>
<string>_playwith._tcp</string>
</array>
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-3940256099942544~1458002511</string>
<key>NSCameraUsageDescription</key>
<string>QR 코드를 스캔하여 방에 접속하기 위해 카메라 권한이 필요합니다.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>채팅방에 사진을 공유하기 위해 갤러리 접근 권한이 필요합니다.</string>
<key>NSCameraUsageDescription</key>
<string>사진을 찍어 공유하기 위해 카메라 권한이 필요합니다.</string>
<key>NSMicrophoneUsageDescription</key>
<string>동영상 촬영을 위해 마이크 권한이 필요합니다.</string>
</dict>
</plist>
+41 -5
View File
@@ -1,5 +1,7 @@
import 'dart:io'; // Platform 확인용
import 'package:flutter/material.dart';
import 'package:playwith_core/playwith_core.dart'; // Core 패키지 import
import 'package:permission_handler/permission_handler.dart';
import 'package:playwith_core/playwith_core.dart';
import 'lobby_screen.dart';
class IntroScreen extends StatefulWidget {
@@ -12,13 +14,47 @@ class IntroScreen extends StatefulWidget {
class _IntroScreenState extends State<IntroScreen> {
final _nicknameController = TextEditingController();
void _enterLobby() {
if (_nicknameController.text.trim().isEmpty) return;
Future<void> _enterLobby() async {
if (_nicknameController.text.trim().isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("닉네임을 입력해주세요.")),
);
return;
}
// 1. Core 패키지의 NetworkManager 초기화
// [수정됨] 플랫폼별 권한 분기 처리
if (Platform.isAndroid) {
// 🤖 안드로이드: 명시적 권한 요청 필요
Map<Permission, PermissionStatus> statuses = await [
Permission.location, // 안드로이드 12 이하
Permission.nearbyWifiDevices, // 안드로이드 13 이상
].request();
// 로그 확인용
bool isNearby = statuses[Permission.nearbyWifiDevices]?.isGranted ?? false;
bool isLocation = statuses[Permission.location]?.isGranted ?? false;
print("Android 권한 Check: Nearby=$isNearby, Location=$isLocation");
// 둘 다 거부되면 진행 불가 (단, 버전에 따라 하나만 있어도 됨)
// 여기서는 "둘 다 false일 때만" 막는 것으로 완화
if (!isNearby && !isLocation) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("❌ 안드로이드는 권한이 필요합니다.")),
);
return;
}
} else if (Platform.isIOS) {
// 🍎 iOS: 별도 요청 불필요
// Info.plist에 설정만 잘 되어 있다면,
// NetworkManager가 start() 될 때 시스템이 알아서 물어봅니다.
print("iOS는 권한 체크를 건너뜁니다. (실행 시 자동 팝업됨)");
}
// 초기화 및 입장
NetworkManager().initialize(nickname: _nicknameController.text.trim());
// 2. 로비 화면으로 이동
if (!mounted) return;
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const LobbyScreen()),
+430 -130
View File
@@ -1,6 +1,10 @@
import 'dart:convert';
import 'package:bonsoir/bonsoir.dart';
import 'package:flutter/material.dart';
import 'package:playwith_core/playwith_core.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:playwith_core/playwith_core.dart'; // GameChatOverlay 포함됨
import 'package:playwith_game_quiz/quiz_game.dart';
import 'package:qr_flutter/qr_flutter.dart';
class LobbyScreen extends StatefulWidget {
const LobbyScreen({super.key});
@@ -10,174 +14,470 @@ class LobbyScreen extends StatefulWidget {
}
class _LobbyScreenState extends State<LobbyScreen> {
final _net = NetworkManager(); // Singleton 인스턴스
final _net = NetworkManager();
final List<String> _logs = [];
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_net.logStream.listen((log) {
if (!mounted) return;
setState(() {
_logs.add(log);
if (_logs.length > 100) _logs.removeAt(0);
});
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_scrollController.hasClients) {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
);
}
});
});
_net.messageStream.listen((data) {
if (data['type'] == 'GAME_START') {
final String gameId = data['gameId'];
if (gameId == 'quiz_ox') {
_startGameAndNavigate(QuizGame());
}
}
});
}
// [핵심] 채팅 오버레이 적용
void _startGameAndNavigate(BaseGame game) {
if (!mounted) return;
game.onStart();
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
Widget gameView;
if (_net.role == NetworkRole.host) {
gameView = game.buildHostView(context);
} else {
gameView = game.buildGuestView(context);
}
// 플랫폼 구조: 게임 화면 위에 채팅창 오버레이
return Stack(
children: [
gameView, // 1. 게임 화면
const SafeArea(
child: GameChatOverlay(), // 2. 채팅창 (Core 제공)
),
],
);
}),
);
}
@override
Widget build(BuildContext context) {
// NetworkManager의 상태(notifyListeners)가 변경될 때마다 화면 다시 그림
return ListenableBuilder(
listenable: _net,
builder: (context, child) {
return Scaffold(
appBar: AppBar(
title: Text('안녕하세요, ${_net.me.nickname}'),
title: Text('대기실: ${_net.me.nickname}'),
actions: [
if (_net.role == NetworkRole.host)
IconButton(
icon: const Icon(Icons.qr_code, size: 30),
onPressed: () => _showHostQRDialog(),
),
if (_net.role != NetworkRole.none)
IconButton(
icon: const Icon(Icons.close),
onPressed: () => _net.stopNetwork(), // 연결 끊기
icon: const Icon(Icons.exit_to_app),
onPressed: () => _net.stopNetwork(),
)
],
),
body: _buildBody(),
body: Column(
children: [
Expanded(flex: 3, child: _buildBody()),
const Divider(thickness: 2, color: Colors.grey),
_buildDebugConsole(),
],
),
);
},
);
}
Widget _buildBody() {
// 1. 아무 역할도 없을 때 -> 선택 화면
if (_net.role == NetworkRole.none) {
return Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_BigButton(
title: "방 만들기\n(Host)",
color: Colors.blue[100]!,
onTap: () => _net.startHosting("${_net.me.nickname}의 방"),
),
_BigButton(
title: "방 찾기\n(Guest)",
color: Colors.green[100]!,
onTap: () => _showRoomListDialog(),
),
],
Widget _buildDebugConsole() {
return Column(
children: [
Container(
width: double.infinity,
padding: const EdgeInsets.all(8.0),
color: Colors.black87,
child: const Text("DEBUG LOGS", style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
),
);
}
// 2. Host 상태일 때 -> 대기실 화면
if (_net.role == NetworkRole.host) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("👑 방장입니다", style: TextStyle(fontSize: 24)),
const SizedBox(height: 20),
const CircularProgressIndicator(),
const SizedBox(height: 20),
const Text("참가자를 기다리는 중..."),
// TODO: 여기에 접속한 게스트 목록 표시 예정
],
),
);
}
// 3. Guest 상태일 때 -> 대기실 화면
if (_net.role == NetworkRole.guest) {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("✅ 접속 완료!", style: TextStyle(fontSize: 24)),
SizedBox(height: 20),
Text("방장이 게임을 시작하기를 기다리세요."),
],
),
);
}
return const SizedBox();
}
// [Guest용] 방 목록 팝업
void _showRoomListDialog() {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text("방 찾는 중..."),
content: SizedBox(
width: double.maxFinite,
height: 300,
child: StreamBuilder<List<BonsoirService>>(
stream: _net.discoverRooms(), // Core의 방 찾기 스트림
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data!.isEmpty) {
return const Center(child: Text("발견된 방이 없습니다.\n(같은 와이파이인지 확인하세요)"));
}
final services = snapshot.data!;
return ListView.builder(
itemCount: services.length,
itemBuilder: (context, index) {
final service = services[index];
// 이름 포맷: "방이름#ID" -> "방이름"만 파싱
final displayName = service.name.split('#').first;
return ListTile(
leading: const Icon(Icons.meeting_room),
title: Text(displayName),
subtitle: Text(service.host ?? "IP 정보 없음"),
onTap: () async {
Navigator.pop(context); // 다이얼로그 닫기
// 해당 방으로 접속 시도 (IP는 service.attributes나 resolve 과정 필요)
// Bonsoir는 service.host에 호스트네임이 들어오므로 resolve 필요
// MVP 단계에서는 간단히:
await _resolveAndJoin(service);
},
);
},
SizedBox(
height: 150,
child: Container(
color: Colors.black,
child: ListView.builder(
controller: _scrollController,
itemCount: _logs.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 2.0),
child: Text(
_logs[index],
style: const TextStyle(color: Colors.greenAccent, fontSize: 12, fontFamily: 'Courier'),
),
);
},
),
),
);
},
),
],
);
}
// Bonsoir Service Resolve (IP 주소 알아내기)
Future<void> _resolveAndJoin(BonsoirService service) async {
// 실제로는 service.resolve() 호출 후 IP 획득 과정을 거쳐야 함.
// Bonsoir 패키지 특성상 resolve가 비동기로 돔.
// MVP 간소화를 위해 service에 host 정보가 있다고 가정하거나
// Broadcast 시점에 attributes에 IP를 넣는 방식을 추천하지만,
// 일단 resolve 시도:
if (service is BonsoirBroadcast) {
// 이미 broadcast 객체라면 바로 정보가 있음 (내가 만든 방)
} else {
await service.resolve(service.resolveRealService);
}
// IP가 ipv4 형태인지 확인 필요. 보통 service.ip 나 attributes 사용
// 여기선 port만 확실하므로, 실제 IP 획득은 Bonsoir 예제 참고 필요
// (테스트 환경에서는 보통 service.attributes에 {'ip': '192.168...'} 넣어서 보냄)
// *중요*: 실제 구현 시 NetworkManager.startHosting에서 attributes에 IP를 넣어주는게 가장 확실함.
// 일단 현재 코드는 로직 흐름만 잡음.
// _net.joinRoom('192.168.0.xxx', service.port);
Widget _buildBody() {
if (_net.role == NetworkRole.none) {
return Center(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_BigButton(
title: "방 만들기\n(Host)",
color: Colors.blue[100]!,
icon: Icons.add_home_work,
onTap: () {
_net.startHosting("${_net.me.nickname}의 방");
Future.delayed(const Duration(milliseconds: 500), () {
if (mounted && _net.role == NetworkRole.host) _showHostQRDialog();
});
},
),
_BigButton(
title: "방 찾기\n(Guest)",
color: Colors.green[100]!,
icon: Icons.search,
onTap: () => _showRoomListDialog(),
),
],
),
const SizedBox(height: 30),
ElevatedButton.icon(
icon: const Icon(Icons.qr_code_scanner, size: 28),
label: const Text("QR 코드로 접속하기"),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 15),
),
onPressed: () => _openQRScanner(),
),
const SizedBox(height: 10),
TextButton(
onPressed: () => _showManualJoinDialog(),
child: const Text("IP 주소 직접 입력 (비상용)", style: TextStyle(color: Colors.grey)),
),
],
),
),
);
}
return Column(
children: [
Container(
padding: const EdgeInsets.all(20.0),
color: Colors.grey[100],
width: double.infinity,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(_net.role == NetworkRole.host ? Icons.wifi_tethering : Icons.wifi, color: Colors.blue),
const SizedBox(width: 10),
Text(
_net.role == NetworkRole.host ? "👑 방장 (나)" : "참가자 (나)",
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
],
),
if (_net.role == NetworkRole.host) ...[
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SelectableText(
"IP: ${_net.hostIp} / Port: ${_net.hostPort}",
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
const SizedBox(width: 10),
InkWell(
onTap: () => _showHostQRDialog(),
child: const Icon(Icons.qr_code, color: Colors.black87),
)
],
),
const SizedBox(height: 5),
const Text("QR 버튼을 눌러 친구들을 초대하세요!", style: TextStyle(fontSize: 12, color: Colors.grey)),
],
],
),
),
const Divider(height: 1),
Expanded(
child: ListView(
padding: const EdgeInsets.all(16),
children: [
const Text("참가자 목록", style: TextStyle(color: Colors.grey)),
const SizedBox(height: 10),
_buildUserTile(_net.me),
..._net.guestList.map((guest) => _buildUserTile(guest)),
if (_net.guestList.isEmpty && _net.role == NetworkRole.host)
const Padding(
padding: EdgeInsets.all(40.0),
child: Center(child: Text("참가자를 기다리는 중...\nQR 코드를 보여주세요.", textAlign: TextAlign.center, style: TextStyle(color: Colors.grey))),
),
],
),
),
_buildReadyButton(),
],
);
}
Widget _buildUserTile(UserInfo user) {
bool isMe = user.id == _net.me.id;
return Card(
elevation: 2,
color: user.isReady ? Colors.green[50] : Colors.white,
margin: const EdgeInsets.symmetric(vertical: 6),
child: ListTile(
leading: CircleAvatar(
backgroundColor: Color(user.colorValue),
child: Text(user.nickname[0], style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
),
title: Text(
user.nickname + (isMe ? " (나)" : ""),
style: const TextStyle(fontWeight: FontWeight.bold),
),
trailing: user.isReady
? const Icon(Icons.check_circle, color: Colors.green, size: 32)
: const Icon(Icons.hourglass_empty, color: Colors.grey, size: 32),
),
);
}
Widget _buildReadyButton() {
bool isReady = _net.me.isReady;
bool canReady = _net.role == NetworkRole.host
? _net.guestList.isNotEmpty
: true;
return Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 10, offset: const Offset(0, -5))],
),
child: ElevatedButton(
onPressed: canReady
? () => _net.toggleReady()
: () {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("참가자가 들어와야 게임을 시작할 수 있습니다.")));
},
style: ElevatedButton.styleFrom(
backgroundColor: !canReady
? Colors.grey
: (isReady ? Colors.redAccent : Colors.blueAccent),
padding: const EdgeInsets.symmetric(vertical: 18),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
elevation: canReady ? 5 : 0,
),
child: Text(
isReady ? "준비 취소 (WAIT)" : "준비 완료 (READY)",
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.white),
),
),
);
}
void _showHostQRDialog() {
if (_net.hostIp == null || _net.hostPort == null) return;
final qrData = jsonEncode({'ip': _net.hostIp, 'port': _net.hostPort});
showDialog(
context: context,
builder: (context) => Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text("초대 QR 코드", style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
const SizedBox(height: 20),
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(border: Border.all(color: Colors.black12), borderRadius: BorderRadius.circular(10)),
child: QrImageView(data: qrData, version: QrVersions.auto, size: 220.0),
),
const SizedBox(height: 20),
Text("IP: ${_net.hostIp} / Port: ${_net.hostPort}", style: const TextStyle(color: Colors.grey)),
const SizedBox(height: 20),
ElevatedButton(onPressed: () => Navigator.pop(context), child: const Text("닫기"))
],
),
),
),
);
}
void _openQRScanner() {
bool isScanCompleted = false;
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => Scaffold(
appBar: AppBar(title: const Text("QR 코드 스캔")),
body: MobileScanner(
onDetect: (capture) {
if (isScanCompleted) return;
final List<Barcode> barcodes = capture.barcodes;
for (final barcode in barcodes) {
final String? code = barcode.rawValue;
if (code != null) {
try {
final data = jsonDecode(code);
if (data['ip'] != null && data['port'] != null) {
isScanCompleted = true;
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("QR 인식 성공! 접속 중...")));
_net.joinRoom(data['ip'], data['port']);
return;
}
} catch (e) {}
}
}
},
),
),
));
}
void _showRoomListDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("방 찾는 중..."),
content: SizedBox(
width: double.maxFinite,
height: 300,
child: StreamBuilder<List<BonsoirService>>(
stream: _net.discoverRooms(),
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data!.isEmpty) {
return const Center(child: Text("검색 중... (로그를 확인하세요)"));
}
final services = snapshot.data!;
return ListView.builder(
itemCount: services.length,
itemBuilder: (context, index) {
final service = services[index];
final displayName = service.name.split('#').first;
final ip = service.attributes?['ip'] ?? '알 수 없음';
return ListTile(
leading: const Icon(Icons.meeting_room),
title: Text(displayName),
subtitle: Text("IP: $ip"),
onTap: () {
Navigator.pop(context);
if (service.attributes != null && service.attributes!['ip'] != null) {
_net.joinRoom(service.attributes!['ip']!, service.port);
}
},
);
},
);
},
),
),
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text("닫기"))],
),
);
}
void _showManualJoinDialog() {
final ipController = TextEditingController(text: "192.168.");
final portController = TextEditingController();
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("수동 접속"),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text("방장 화면의 IP/Port를 입력하세요."),
TextField(controller: ipController, decoration: const InputDecoration(labelText: "IP")),
TextField(controller: portController, decoration: const InputDecoration(labelText: "Port")),
],
),
actions: [
TextButton(onPressed: () => Navigator.pop(context), child: const Text("취소")),
ElevatedButton(
onPressed: () {
final ip = ipController.text.trim();
final port = int.tryParse(portController.text.trim());
if (ip.isNotEmpty && port != null) {
Navigator.pop(context);
_net.joinRoom(ip, port);
}
},
child: const Text("접속"),
),
],
),
);
}
}
class _BigButton extends StatelessWidget {
final String title;
final Color color;
final IconData icon;
final VoidCallback onTap;
const _BigButton({required this.title, required this.color, required this.onTap});
const _BigButton({required this.title, required this.color, required this.icon, required this.onTap});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
width: 150,
height: 150,
decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(20)),
child: Center(child: Text(title, textAlign: TextAlign.center, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold))),
width: 140, height: 140,
decoration: BoxDecoration(
color: color, borderRadius: BorderRadius.circular(20),
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 10, offset: const Offset(0, 5))],
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 40, color: Colors.black54),
const SizedBox(height: 10),
Text(title, textAlign: TextAlign.center, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
],
),
),
);
}
+49 -1
View File
@@ -1,13 +1,61 @@
import 'package:flutter/material.dart';
import 'package:playwith_core/playwith_core.dart';
import 'package:playwith_game_quiz/quiz_game.dart'; // 퀴즈 모듈 import
import 'intro_screen.dart';
void main() {
// 1. 플러터 바인딩 초기화
WidgetsFlutterBinding.ensureInitialized();
// 2. 사운드 리소스 주입 (여기가 핵심!)
// AssetSource는 'assets/' 접두어를 자동으로 붙이므로 그 하위 경로만 적습니다.
SoundManager().initialize(soundPaths: {
SoundKey.bgm: 'audio/bgm.mp3',
SoundKey.correct: 'audio/correct.mp3',
SoundKey.wrong: 'audio/wrong.mp3',
SoundKey.win: 'audio/win.mp3',
});
runApp(const PlayWithApp());
}
class PlayWithApp extends StatelessWidget {
class PlayWithApp extends StatefulWidget {
const PlayWithApp({super.key});
@override
State<PlayWithApp> createState() => _PlayWithAppState();
}
class _PlayWithAppState extends State<PlayWithApp> {
final _net = NetworkManager();
// 등록된 게임 목록
final List<BaseGame> _games = [
QuizGame(), // 여기서 등록!
];
@override
void initState() {
super.initState();
// [전역 라우팅] 네트워크 메시지를 감시하다가 'GAME_START'가 오면 해당 게임 실행
_net.messageStream.listen((data) {
if (data['type'] == 'GAME_START') {
final String gameId = data['gameId'];
// ID에 맞는 게임 찾기
final game = _games.firstWhere(
(g) => g.id == gameId,
orElse: () => throw Exception("Game not found: $gameId")
);
// 게임 화면으로 이동 (네비게이터 키를 안 쓰고 있어서 간단히 처리 불가, 아래 설명 참조)
// 실제로는 GlobalKey<NavigatorState>를 쓰거나, 현재 context를 찾아야 함.
// MVP에서는 LobbyScreen 내부에서 처리하는 것이 안전함.
}
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
@@ -6,6 +6,18 @@
#include "generated_plugin_registrant.h"
#include <audioplayers_linux/audioplayers_linux_plugin.h>
#include <file_selector_linux/file_selector_plugin.h>
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin");
audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar);
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin");
sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar);
}
@@ -3,6 +3,9 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_linux
file_selector_linux
sqlite3_flutter_libs
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
@@ -5,8 +5,16 @@
import FlutterMacOS
import Foundation
import audioplayers_darwin
import bonsoir_darwin
import file_selector_macos
import mobile_scanner
import sqlite3_flutter_libs
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
SwiftBonsoirPlugin.register(with: registry.registrar(forPlugin: "SwiftBonsoirPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))
}
+486 -9
View File
@@ -1,6 +1,14 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.7.0"
async:
dependency: transitive
description:
@@ -9,38 +17,110 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.13.0"
audioplayers:
dependency: "direct main"
description:
name: audioplayers
sha256: "5441fa0ceb8807a5ad701199806510e56afde2b4913d9d17c2f19f2902cf0ae4"
url: "https://pub.dev"
source: hosted
version: "6.5.1"
audioplayers_android:
dependency: transitive
description:
name: audioplayers_android
sha256: "60a6728277228413a85755bd3ffd6fab98f6555608923813ce383b190a360605"
url: "https://pub.dev"
source: hosted
version: "5.2.1"
audioplayers_darwin:
dependency: transitive
description:
name: audioplayers_darwin
sha256: "0811d6924904ca13f9ef90d19081e4a87f7297ddc19fc3d31f60af1aaafee333"
url: "https://pub.dev"
source: hosted
version: "6.3.0"
audioplayers_linux:
dependency: transitive
description:
name: audioplayers_linux
sha256: f75bce1ce864170ef5e6a2c6a61cd3339e1a17ce11e99a25bae4474ea491d001
url: "https://pub.dev"
source: hosted
version: "4.2.1"
audioplayers_platform_interface:
dependency: transitive
description:
name: audioplayers_platform_interface
sha256: "0e2f6a919ab56d0fec272e801abc07b26ae7f31980f912f24af4748763e5a656"
url: "https://pub.dev"
source: hosted
version: "7.1.1"
audioplayers_web:
dependency: transitive
description:
name: audioplayers_web
sha256: "1c0f17cec68455556775f1e50ca85c40c05c714a99c5eb1d2d57cc17ba5522d7"
url: "https://pub.dev"
source: hosted
version: "5.1.1"
audioplayers_windows:
dependency: transitive
description:
name: audioplayers_windows
sha256: "4048797865105b26d47628e6abb49231ea5de84884160229251f37dfcbe52fd7"
url: "https://pub.dev"
source: hosted
version: "4.2.1"
bonsoir:
dependency: transitive
description:
name: bonsoir
sha256: d5b0cb2d38ac8b0057990e3046ef0e4552b63f6d636284c7e57ac2b257e2000a
sha256: "15de9d734708ccce1484ea9bbc750e364af88cb30fe6392368dc0cf233ddee5e"
url: "https://pub.dev"
source: hosted
version: "2.2.0+1"
version: "6.0.1"
bonsoir_android:
dependency: transitive
description:
name: bonsoir_android
sha256: "21c38f707df0755b5bedd05c5fd52cb5e9fa9f4a4efbcef94cb9669029c73d75"
sha256: e19728f94a0d9813abf9e2edf644fede008e58ef539865a1be86ac5d8994154e
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "6.0.1"
bonsoir_darwin:
dependency: transitive
description:
name: bonsoir_darwin
sha256: fe36cb2acec37c175213364a91ae1b2866a47e061f18b11322ac8e0ca39d5e61
sha256: e242a03a019fd474be657715826cfc13e43d02c88e46ec5611a20b9d4f72854d
url: "https://pub.dev"
source: hosted
version: "2.2.0+1"
version: "6.0.1"
bonsoir_linux:
dependency: transitive
description:
name: bonsoir_linux
sha256: "5f40f8fa6dc79245ddde38f4440bc0f49cd25ee6be04a4e56fe9fca0d2be7998"
url: "https://pub.dev"
source: hosted
version: "6.0.1"
bonsoir_platform_interface:
dependency: transitive
description:
name: bonsoir_platform_interface
sha256: "97081d861ff2e7b45edd9e17ae1b35530d5a7ec561e3377e00da2e0cfae500dc"
sha256: "3fa0c46b30eb2a2f48be6fa53591a5c0425bf00520be761b61763e58b51814ff"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "6.0.1"
bonsoir_windows:
dependency: transitive
description:
name: bonsoir_windows
sha256: "34c54802baaa2f00e3c4ab7ea46888f2a829876753778e2f40e3f273c3382d34"
url: "https://pub.dev"
source: hosted
version: "6.0.1"
boolean_selector:
dependency: transitive
description:
@@ -73,6 +153,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.19.1"
convert:
dependency: transitive
description:
name: convert
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
url: "https://pub.dev"
source: hosted
version: "3.1.2"
cross_file:
dependency: transitive
description:
name: cross_file
sha256: "701dcfc06da0882883a2657c445103380e53e647060ad8d9dfb710c100996608"
url: "https://pub.dev"
source: hosted
version: "0.3.5+1"
crypto:
dependency: transitive
description:
@@ -81,6 +177,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.7"
dbus:
dependency: transitive
description:
name: dbus
sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c"
url: "https://pub.dev"
source: hosted
version: "0.7.11"
drift:
dependency: transitive
description:
name: drift
sha256: "83290a32ae006a7535c5ecf300722cb77177250d9df4ee2becc5fa8a36095114"
url: "https://pub.dev"
source: hosted
version: "2.29.0"
equatable:
dependency: transitive
description:
@@ -97,6 +209,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.3"
ffi:
dependency: transitive
description:
name: ffi
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.1"
file_picker:
dependency: transitive
description:
name: file_picker
sha256: "1bbf65dd997458a08b531042ec3794112a6c39c07c37ff22113d2e7e4f81d4e4"
url: "https://pub.dev"
source: hosted
version: "6.2.1"
file_selector_linux:
dependency: transitive
description:
name: file_selector_linux
sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0"
url: "https://pub.dev"
source: hosted
version: "0.9.4"
file_selector_macos:
dependency: transitive
description:
name: file_selector_macos
sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a"
url: "https://pub.dev"
source: hosted
version: "0.9.5"
file_selector_platform_interface:
dependency: transitive
description:
name: file_selector_platform_interface
sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85"
url: "https://pub.dev"
source: hosted
version: "2.7.0"
file_selector_windows:
dependency: transitive
description:
name: file_selector_windows
sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd"
url: "https://pub.dev"
source: hosted
version: "0.9.3+5"
fixnum:
dependency: transitive
description:
@@ -118,11 +286,104 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.0.0"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1
url: "https://pub.dev"
source: hosted
version: "2.0.33"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
http:
dependency: transitive
description:
name: http
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
url: "https://pub.dev"
source: hosted
version: "1.6.0"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
image_picker:
dependency: transitive
description:
name: image_picker
sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
image_picker_android:
dependency: transitive
description:
name: image_picker_android
sha256: "5e9bf126c37c117cf8094215373c6d561117a3cfb50ebc5add1a61dc6e224677"
url: "https://pub.dev"
source: hosted
version: "0.8.13+10"
image_picker_for_web:
dependency: transitive
description:
name: image_picker_for_web
sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
image_picker_ios:
dependency: transitive
description:
name: image_picker_ios
sha256: "997d100ce1dda5b1ba4085194c5e36c9f8a1fb7987f6a36ab677a344cd2dc986"
url: "https://pub.dev"
source: hosted
version: "0.8.13+2"
image_picker_linux:
dependency: transitive
description:
name: image_picker_linux
sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4"
url: "https://pub.dev"
source: hosted
version: "0.2.2"
image_picker_macos:
dependency: transitive
description:
name: image_picker_macos
sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91"
url: "https://pub.dev"
source: hosted
version: "0.2.2+1"
image_picker_platform_interface:
dependency: transitive
description:
name: image_picker_platform_interface
sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c"
url: "https://pub.dev"
source: hosted
version: "2.11.1"
image_picker_windows:
dependency: transitive
description:
name: image_picker_windows
sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae
url: "https://pub.dev"
source: hosted
version: "0.2.2"
leak_tracker:
dependency: transitive
description:
@@ -179,6 +440,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.16.0"
mime:
dependency: transitive
description:
name: mime
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
mobile_scanner:
dependency: "direct main"
description:
name: mobile_scanner
sha256: d234581c090526676fd8fab4ada92f35c6746e3fb4f05a399665d75a399fb760
url: "https://pub.dev"
source: hosted
version: "5.2.3"
objective_c:
dependency: transitive
description:
name: objective_c
sha256: "1f81ed9e41909d44162d7ec8663b2c647c202317cc0b56d3d56f6a13146a0b64"
url: "https://pub.dev"
source: hosted
version: "9.1.0"
path:
dependency: transitive
description:
@@ -187,6 +472,118 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.1"
path_provider:
dependency: transitive
description:
name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
url: "https://pub.dev"
source: hosted
version: "2.1.5"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e
url: "https://pub.dev"
source: hosted
version: "2.2.22"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "6192e477f34018ef1ea790c56fffc7302e3bc3efede9e798b934c252c8c105ba"
url: "https://pub.dev"
source: hosted
version: "2.5.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
permission_handler:
dependency: "direct main"
description:
name: permission_handler
sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849"
url: "https://pub.dev"
source: hosted
version: "11.4.0"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc
url: "https://pub.dev"
source: hosted
version: "12.1.0"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023
url: "https://pub.dev"
source: hosted
version: "9.4.7"
permission_handler_html:
dependency: transitive
description:
name: permission_handler_html
sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
url: "https://pub.dev"
source: hosted
version: "0.1.3+5"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
url: "https://pub.dev"
source: hosted
version: "4.3.0"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
url: "https://pub.dev"
source: hosted
version: "0.2.1"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1"
url: "https://pub.dev"
source: hosted
version: "7.0.1"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
playwith_core:
dependency: "direct main"
description:
@@ -209,6 +606,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.8"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
qr:
dependency: transitive
description:
name: qr
sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
qr_flutter:
dependency: "direct main"
description:
name: qr_flutter
sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097"
url: "https://pub.dev"
source: hosted
version: "4.1.0"
sky_engine:
dependency: transitive
description: flutter
@@ -222,6 +643,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.10.1"
sqlite3:
dependency: transitive
description:
name: sqlite3
sha256: "3145bd74dcdb4fd6f5c6dda4d4e4490a8087d7f286a14dee5d37087290f0f8a2"
url: "https://pub.dev"
source: hosted
version: "2.9.4"
sqlite3_flutter_libs:
dependency: transitive
description:
name: sqlite3_flutter_libs
sha256: "69c80d812ef2500202ebd22002cbfc1b6565e9ff56b2f971e757fac5d42294df"
url: "https://pub.dev"
source: hosted
version: "0.5.40"
stack_trace:
dependency: transitive
description:
@@ -246,6 +683,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.1"
synchronized:
dependency: transitive
description:
name: synchronized
sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0
url: "https://pub.dev"
source: hosted
version: "3.4.0"
term_glyph:
dependency: transitive
description:
@@ -294,6 +739,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "15.0.2"
web:
dependency: transitive
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
win32:
dependency: transitive
description:
name: win32
sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e
url: "https://pub.dev"
source: hosted
version: "5.15.0"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
url: "https://pub.dev"
source: hosted
version: "6.6.1"
sdks:
dart: ">=3.9.2 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"
flutter: ">=3.35.0"
+7 -35
View File
@@ -35,6 +35,11 @@ dependencies:
path: ../../packages/core
playwith_game_quiz:
path: ../../packages/games/quiz
permission_handler: ^11.0.0
qr_flutter: ^4.1.0
mobile_scanner: ^5.1.0
audioplayers: ^6.0.0
dev_dependencies:
flutter_test:
@@ -52,39 +57,6 @@ dev_dependencies:
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# 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/to/resolution-aware-images
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/to/asset-from-package
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package
assets:
- assets/audio/
@@ -6,6 +6,21 @@
#include "generated_plugin_registrant.h"
#include <audioplayers_windows/audioplayers_windows_plugin.h>
#include <bonsoir_windows/bonsoir_windows_plugin_c_api.h>
#include <file_selector_windows/file_selector_windows.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
AudioplayersWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
BonsoirWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("BonsoirWindowsPluginCApi"));
FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
Sqlite3FlutterLibsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin"));
}
@@ -3,6 +3,11 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_windows
bonsoir_windows
file_selector_windows
permission_handler_windows
sqlite3_flutter_libs
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST