mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 19:11:45 +00:00
@@ -901,6 +901,18 @@ class ApplicationMock: ApplicationProtocol {
|
|||||||
openReceivedInvocations.append(url)
|
openReceivedInvocations.append(url)
|
||||||
openClosure?(url)
|
openClosure?(url)
|
||||||
}
|
}
|
||||||
|
//MARK: - openAppSettings
|
||||||
|
|
||||||
|
var openAppSettingsCallsCount = 0
|
||||||
|
var openAppSettingsCalled: Bool {
|
||||||
|
return openAppSettingsCallsCount > 0
|
||||||
|
}
|
||||||
|
var openAppSettingsClosure: (() -> Void)?
|
||||||
|
|
||||||
|
func openAppSettings() {
|
||||||
|
openAppSettingsCallsCount += 1
|
||||||
|
openAppSettingsClosure?()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
class AudioConverterMock: AudioConverterProtocol {
|
class AudioConverterMock: AudioConverterProtocol {
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ struct HeroImage: View {
|
|||||||
case normal
|
case normal
|
||||||
case positive
|
case positive
|
||||||
case subtle
|
case subtle
|
||||||
|
case critical
|
||||||
|
|
||||||
var foregroundColor: Color {
|
var foregroundColor: Color {
|
||||||
switch self {
|
switch self {
|
||||||
@@ -34,6 +35,8 @@ struct HeroImage: View {
|
|||||||
return .compound.iconSuccessPrimary
|
return .compound.iconSuccessPrimary
|
||||||
case .subtle:
|
case .subtle:
|
||||||
return .compound.iconSecondary
|
return .compound.iconSecondary
|
||||||
|
case .critical:
|
||||||
|
return .compound.iconCriticalPrimary
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,6 +48,8 @@ struct HeroImage: View {
|
|||||||
return .compound.bgSuccessSubtle
|
return .compound.bgSuccessSubtle
|
||||||
case .subtle:
|
case .subtle:
|
||||||
return .compound.bgSubtlePrimary
|
return .compound.bgSubtlePrimary
|
||||||
|
case .critical:
|
||||||
|
return .compound.bgCanvasDefault
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,11 +49,7 @@ final class StaticLocationScreenCoordinator: CoordinatorProtocol {
|
|||||||
case .close:
|
case .close:
|
||||||
actionsSubject.send(.close)
|
actionsSubject.send(.close)
|
||||||
case .openSystemSettings:
|
case .openSystemSettings:
|
||||||
guard let url = URL(string: UIApplication.openSettingsURLString),
|
UIApplication.shared.openAppSettings()
|
||||||
UIApplication.shared.canOpenURL(url) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
UIApplication.shared.open(url)
|
|
||||||
case .sendLocation(let geoURI, let isUserLocation):
|
case .sendLocation(let geoURI, let isUserLocation):
|
||||||
actionsSubject.send(.selectedLocation(geoURI, isUserLocation: isUserLocation))
|
actionsSubject.send(.selectedLocation(geoURI, isUserLocation: isUserLocation))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ final class QRCodeLoginScreenCoordinator: CoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init(parameters: QRCodeLoginScreenCoordinatorParameters) {
|
init(parameters: QRCodeLoginScreenCoordinatorParameters) {
|
||||||
viewModel = QRCodeLoginScreenViewModel(qrCodeLoginService: parameters.qrCodeLoginService)
|
viewModel = QRCodeLoginScreenViewModel(qrCodeLoginService: parameters.qrCodeLoginService,
|
||||||
|
application: UIApplication.shared)
|
||||||
orientationManager = parameters.orientationManager
|
orientationManager = parameters.orientationManager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ enum QRCodeLoginScreenViewModelAction {
|
|||||||
struct QRCodeLoginScreenViewState: BindableState {
|
struct QRCodeLoginScreenViewState: BindableState {
|
||||||
var state: QRCodeLoginState = .initial
|
var state: QRCodeLoginState = .initial
|
||||||
|
|
||||||
private let listItem3AttributedText = {
|
private static let initialStateListItem3AttributedText = {
|
||||||
let boldPlaceholder = "{bold}"
|
let boldPlaceholder = "{bold}"
|
||||||
var finalString = AttributedString(L10n.screenQrCodeLoginInitialStateItem3(boldPlaceholder))
|
var finalString = AttributedString(L10n.screenQrCodeLoginInitialStateItem3(boldPlaceholder))
|
||||||
var boldString = AttributedString(L10n.screenQrCodeLoginInitialStateItem3Action)
|
var boldString = AttributedString(L10n.screenQrCodeLoginInitialStateItem3Action)
|
||||||
@@ -32,7 +32,7 @@ struct QRCodeLoginScreenViewState: BindableState {
|
|||||||
return finalString
|
return finalString
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private let listItem4AttributedText = {
|
private static let initialStateListItem4AttributedText = {
|
||||||
let boldPlaceholder = "{bold}"
|
let boldPlaceholder = "{bold}"
|
||||||
var finalString = AttributedString(L10n.screenQrCodeLoginInitialStateItem4(boldPlaceholder))
|
var finalString = AttributedString(L10n.screenQrCodeLoginInitialStateItem4(boldPlaceholder))
|
||||||
var boldString = AttributedString(L10n.screenQrCodeLoginInitialStateItem4Action)
|
var boldString = AttributedString(L10n.screenQrCodeLoginInitialStateItem4Action)
|
||||||
@@ -41,19 +41,24 @@ struct QRCodeLoginScreenViewState: BindableState {
|
|||||||
return finalString
|
return finalString
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var listItems: [AttributedString] {
|
let initialStateListItems = [
|
||||||
[
|
AttributedString(L10n.screenQrCodeLoginInitialStateItem1(InfoPlistReader.main.productionAppName)),
|
||||||
AttributedString(L10n.screenQrCodeLoginInitialStateItem1(InfoPlistReader.main.productionAppName)),
|
AttributedString(L10n.screenQrCodeLoginInitialStateItem2),
|
||||||
AttributedString(L10n.screenQrCodeLoginInitialStateItem2),
|
initialStateListItem3AttributedText,
|
||||||
listItem3AttributedText,
|
initialStateListItem4AttributedText
|
||||||
listItem4AttributedText
|
]
|
||||||
]
|
|
||||||
}
|
let connectionNotSecureListItems = [
|
||||||
|
AttributedString(L10n.screenQrCodeLoginConnectionNoteSecureStateListItem1),
|
||||||
|
AttributedString(L10n.screenQrCodeLoginConnectionNoteSecureStateListItem2),
|
||||||
|
AttributedString(L10n.screenQrCodeLoginConnectionNoteSecureStateListItem3)
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
enum QRCodeLoginScreenViewAction {
|
enum QRCodeLoginScreenViewAction {
|
||||||
case cancel
|
case cancel
|
||||||
case startScan
|
case startScan
|
||||||
|
case openSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
enum QRCodeLoginState: Equatable {
|
enum QRCodeLoginState: Equatable {
|
||||||
@@ -66,6 +71,8 @@ enum QRCodeLoginState: Equatable {
|
|||||||
|
|
||||||
enum QRCodeLoginErrorState: Equatable {
|
enum QRCodeLoginErrorState: Equatable {
|
||||||
case noCameraPermission
|
case noCameraPermission
|
||||||
|
case connectionNotSecure
|
||||||
|
case unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
enum QRCodeLoginScanningState: Equatable {
|
enum QRCodeLoginScanningState: Equatable {
|
||||||
|
|||||||
@@ -22,14 +22,17 @@ typealias QRCodeLoginScreenViewModelType = StateStoreViewModel<QRCodeLoginScreen
|
|||||||
|
|
||||||
class QRCodeLoginScreenViewModel: QRCodeLoginScreenViewModelType, QRCodeLoginScreenViewModelProtocol {
|
class QRCodeLoginScreenViewModel: QRCodeLoginScreenViewModelType, QRCodeLoginScreenViewModelProtocol {
|
||||||
private let qrCodeLoginService: QRCodeLoginServiceProtocol
|
private let qrCodeLoginService: QRCodeLoginServiceProtocol
|
||||||
|
private let application: ApplicationProtocol
|
||||||
|
|
||||||
private let actionsSubject: PassthroughSubject<QRCodeLoginScreenViewModelAction, Never> = .init()
|
private let actionsSubject: PassthroughSubject<QRCodeLoginScreenViewModelAction, Never> = .init()
|
||||||
var actionsPublisher: AnyPublisher<QRCodeLoginScreenViewModelAction, Never> {
|
var actionsPublisher: AnyPublisher<QRCodeLoginScreenViewModelAction, Never> {
|
||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(qrCodeLoginService: QRCodeLoginServiceProtocol) {
|
init(qrCodeLoginService: QRCodeLoginServiceProtocol,
|
||||||
|
application: ApplicationProtocol) {
|
||||||
self.qrCodeLoginService = qrCodeLoginService
|
self.qrCodeLoginService = qrCodeLoginService
|
||||||
|
self.application = application
|
||||||
super.init(initialViewState: QRCodeLoginScreenViewState())
|
super.init(initialViewState: QRCodeLoginScreenViewState())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +44,8 @@ class QRCodeLoginScreenViewModel: QRCodeLoginScreenViewModelType, QRCodeLoginScr
|
|||||||
actionsSubject.send(.cancel)
|
actionsSubject.send(.cancel)
|
||||||
case .startScan:
|
case .startScan:
|
||||||
Task { await startScanIfPossible() }
|
Task { await startScanIfPossible() }
|
||||||
|
case .openSettings:
|
||||||
|
application.openAppSettings()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +56,7 @@ class QRCodeLoginScreenViewModel: QRCodeLoginScreenViewModelType, QRCodeLoginScr
|
|||||||
/// Only for mocking initial states
|
/// Only for mocking initial states
|
||||||
fileprivate init(state: QRCodeLoginState) {
|
fileprivate init(state: QRCodeLoginState) {
|
||||||
qrCodeLoginService = QRCodeLoginServiceMock(configuration: .init())
|
qrCodeLoginService = QRCodeLoginServiceMock(configuration: .init())
|
||||||
|
application = ApplicationMock()
|
||||||
super.init(initialViewState: .init(state: state))
|
super.init(initialViewState: .init(state: state))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,8 +40,7 @@ struct QRCodeLoginScreen: View {
|
|||||||
case .scan:
|
case .scan:
|
||||||
qrScanContent
|
qrScanContent
|
||||||
case .error:
|
case .error:
|
||||||
// TODO: Handle error states
|
errorContent
|
||||||
EmptyView()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +57,7 @@ struct QRCodeLoginScreen: View {
|
|||||||
}
|
}
|
||||||
.padding(.horizontal, 24)
|
.padding(.horizontal, 24)
|
||||||
|
|
||||||
SFNumberedListView(items: context.viewState.listItems)
|
SFNumberedListView(items: context.viewState.initialStateListItems)
|
||||||
}
|
}
|
||||||
} bottomContent: {
|
} bottomContent: {
|
||||||
Button(L10n.actionContinue) {
|
Button(L10n.actionContinue) {
|
||||||
@@ -107,7 +106,7 @@ struct QRCodeLoginScreen: View {
|
|||||||
case .invalid:
|
case .invalid:
|
||||||
VStack(spacing: 16) {
|
VStack(spacing: 16) {
|
||||||
Button(L10n.screenQrCodeLoginInvalidScanStateRetryButton) {
|
Button(L10n.screenQrCodeLoginInvalidScanStateRetryButton) {
|
||||||
// TODO: Implement try again
|
context.send(viewAction: .startScan)
|
||||||
}
|
}
|
||||||
.buttonStyle(.compound(.primary))
|
.buttonStyle(.compound(.primary))
|
||||||
|
|
||||||
@@ -125,7 +124,7 @@ struct QRCodeLoginScreen: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var qrScanner: some View {
|
private var qrScanner: some View {
|
||||||
QRCodeScannerView()
|
QRCodeScannerView()
|
||||||
.aspectRatio(1.0, contentMode: .fill)
|
.aspectRatio(1.0, contentMode: .fill)
|
||||||
@@ -136,7 +135,7 @@ struct QRCodeLoginScreen: View {
|
|||||||
QRScannerViewOverlay(length: qrFrame.height)
|
QRScannerViewOverlay(length: qrFrame.height)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ToolbarContentBuilder
|
@ToolbarContentBuilder
|
||||||
private var toolbar: some ToolbarContent {
|
private var toolbar: some ToolbarContent {
|
||||||
ToolbarItem(placement: .cancellationAction) {
|
ToolbarItem(placement: .cancellationAction) {
|
||||||
@@ -145,6 +144,98 @@ struct QRCodeLoginScreen: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var errorContent: some View {
|
||||||
|
if case let .error(errorState) = context.viewState.state {
|
||||||
|
FullscreenDialog {
|
||||||
|
errorContentHeader(errorState: errorState)
|
||||||
|
} bottomContent: {
|
||||||
|
errorContentFooter(errorState: errorState)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 24)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func errorContentHeader(errorState: QRCodeLoginState.QRCodeLoginErrorState) -> some View {
|
||||||
|
switch errorState {
|
||||||
|
case .noCameraPermission:
|
||||||
|
VStack(spacing: 16) {
|
||||||
|
HeroImage(icon: \.takePhotoSolid, style: .subtle)
|
||||||
|
|
||||||
|
VStack(spacing: 8) {
|
||||||
|
Text(L10n.screenQrCodeLoginNoCameraPermissionStateTitle)
|
||||||
|
.foregroundColor(.compound.textPrimary)
|
||||||
|
.font(.compound.headingMDBold)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
|
||||||
|
Text(L10n.screenQrCodeLoginNoCameraPermissionStateDescription)
|
||||||
|
.foregroundColor(.compound.textSecondary)
|
||||||
|
.font(.compound.bodyMD)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .connectionNotSecure:
|
||||||
|
VStack(spacing: 40) {
|
||||||
|
VStack(spacing: 16) {
|
||||||
|
HeroImage(icon: \.error, style: .critical)
|
||||||
|
|
||||||
|
VStack(spacing: 8) {
|
||||||
|
Text(L10n.screenQrCodeLoginConnectionNoteSecureStateTitle)
|
||||||
|
.foregroundColor(.compound.textPrimary)
|
||||||
|
.font(.compound.headingMDBold)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
|
||||||
|
Text(L10n.screenQrCodeLoginConnectionNoteSecureStateDescription)
|
||||||
|
.foregroundColor(.compound.textSecondary)
|
||||||
|
.font(.compound.bodyMD)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VStack(spacing: 24) {
|
||||||
|
Text(L10n.screenQrCodeLoginConnectionNoteSecureStateListHeader)
|
||||||
|
.foregroundColor(.compound.textPrimary)
|
||||||
|
.font(.compound.bodyLGSemibold)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
|
||||||
|
SFNumberedListView(items: context.viewState.connectionNotSecureListItems)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .unknown:
|
||||||
|
VStack(spacing: 16) {
|
||||||
|
HeroImage(icon: \.error, style: .critical)
|
||||||
|
|
||||||
|
VStack(spacing: 8) {
|
||||||
|
Text(L10n.commonSomethingWentWrong)
|
||||||
|
.foregroundColor(.compound.textPrimary)
|
||||||
|
.font(.compound.headingMDBold)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
|
||||||
|
Text(L10n.screenQrCodeLoginUnknownErrorDescription)
|
||||||
|
.foregroundColor(.compound.textSecondary)
|
||||||
|
.font(.compound.bodyMD)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func errorContentFooter(errorState: QRCodeLoginState.QRCodeLoginErrorState) -> some View {
|
||||||
|
switch errorState {
|
||||||
|
case .noCameraPermission:
|
||||||
|
Button(L10n.screenQrCodeLoginNoCameraPermissionButton) {
|
||||||
|
context.send(viewAction: .openSettings)
|
||||||
|
}
|
||||||
|
.buttonStyle(.compound(.primary))
|
||||||
|
case .connectionNotSecure, .unknown:
|
||||||
|
Button(L10n.screenQrCodeLoginStartOverButton) {
|
||||||
|
context.send(viewAction: .startScan)
|
||||||
|
}
|
||||||
|
.buttonStyle(.compound(.primary))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct QRScannerViewOverlay: View {
|
private struct QRScannerViewOverlay: View {
|
||||||
@@ -183,6 +274,12 @@ struct QRCodeLoginScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
|
|
||||||
static let invalidStateViewModel = QRCodeLoginScreenViewModel.mock(state: .scan(.invalid))
|
static let invalidStateViewModel = QRCodeLoginScreenViewModel.mock(state: .scan(.invalid))
|
||||||
|
|
||||||
|
static let noCameraPermissionStateViewModel = QRCodeLoginScreenViewModel.mock(state: .error(.noCameraPermission))
|
||||||
|
|
||||||
|
static let connectionNotSecureStateViewModel = QRCodeLoginScreenViewModel.mock(state: .error(.connectionNotSecure))
|
||||||
|
|
||||||
|
static let unknownErrorStateViewModel = QRCodeLoginScreenViewModel.mock(state: .error(.unknown))
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
QRCodeLoginScreen(context: initialStateViewModel.context)
|
QRCodeLoginScreen(context: initialStateViewModel.context)
|
||||||
.previewDisplayName("Initial")
|
.previewDisplayName("Initial")
|
||||||
@@ -195,5 +292,14 @@ struct QRCodeLoginScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
|
|
||||||
QRCodeLoginScreen(context: invalidStateViewModel.context)
|
QRCodeLoginScreen(context: invalidStateViewModel.context)
|
||||||
.previewDisplayName("Invalid")
|
.previewDisplayName("Invalid")
|
||||||
|
|
||||||
|
QRCodeLoginScreen(context: noCameraPermissionStateViewModel.context)
|
||||||
|
.previewDisplayName("No Camera Permission")
|
||||||
|
|
||||||
|
QRCodeLoginScreen(context: connectionNotSecureStateViewModel.context)
|
||||||
|
.previewDisplayName("Connection not secure")
|
||||||
|
|
||||||
|
QRCodeLoginScreen(context: unknownErrorStateViewModel.context)
|
||||||
|
.previewDisplayName("Unknown error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -619,8 +619,7 @@ class RoomScreenInteractionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func openSystemSettings() {
|
private func openSystemSettings() {
|
||||||
guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
|
application.openAppSettings()
|
||||||
application.open(url)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func displayMediaActionIfPossible(timelineItem: RoomTimelineItemProtocol) async -> RoomTimelineControllerAction {
|
private func displayMediaActionIfPossible(timelineItem: RoomTimelineItemProtocol) async -> RoomTimelineControllerAction {
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ protocol ApplicationProtocol {
|
|||||||
func endBackgroundTask(_ identifier: UIBackgroundTaskIdentifier)
|
func endBackgroundTask(_ identifier: UIBackgroundTaskIdentifier)
|
||||||
|
|
||||||
func open(_ url: URL)
|
func open(_ url: URL)
|
||||||
|
|
||||||
|
func openAppSettings()
|
||||||
|
|
||||||
var backgroundTimeRemaining: TimeInterval { get }
|
var backgroundTimeRemaining: TimeInterval { get }
|
||||||
|
|
||||||
@@ -34,4 +36,11 @@ extension UIApplication: ApplicationProtocol {
|
|||||||
func open(_ url: URL) {
|
func open(_ url: URL) {
|
||||||
open(url, options: [:], completionHandler: nil)
|
open(url, options: [:], completionHandler: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func openAppSettings() {
|
||||||
|
guard let url = URL(string: UIApplication.openSettingsURLString) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
open(url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:81808a0d38699a96abea059176bf065956bbbe2f457cb58bd4f8ec008e043807
|
||||||
|
size 240316
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:51b58490668adc1c908ec4ecc3e8a95f2c2c47a246f293d559c5e6a9a7f2d8b3
|
||||||
|
size 154291
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:19143f0cb869a917d7bff9d6c510ad5c589dad58f2f6a1f96c43402c8cb7eaca
|
||||||
|
size 130219
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:e532918bbc3dd6f19b1c6f595bf4e78328777f67ba168ca73820acf36c4b5fb3
|
||||||
|
size 364040
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:45919d107396913c55c1bc4eb9a2cc3aeca0778399d3cc3fad55bf03fe710f80
|
||||||
|
size 221041
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:d2fc9f0ebae6cf4c1521ffbde06fbdad3bc15071db2a9461abf1b7d1415f113e
|
||||||
|
size 164912
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:9d2b9794ad3b7d038113d0b87bd0f694194471adb29357e963f8143d955dc4ac
|
||||||
|
size 164424
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:d19862c113e8168a9d05d81cb03c5f18fd29cfa22eed9fdc4ddef654c920c0c0
|
||||||
|
size 98538
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:4a70dec75d62f4ebb3d00f613c3314c741bef3cc757d5c9b78358ca8b9535d52
|
||||||
|
size 78473
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:54b0bc2bacf2504febf374970d5afdbf8f8f03b139005c5c81d04761faf66132
|
||||||
|
size 268685
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:a0e59c61d996514d1f3698856e8ad26cd405a036e2e57dcd6cb616ced0c27b6d
|
||||||
|
size 154608
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:5d7c1eb55621a0c337ecc6c7882fdd6c48d55ca6ba9bceaaa7e334dc123b0b1e
|
||||||
|
size 111028
|
||||||
Reference in New Issue
Block a user