Initial commit
This commit is contained in:
commit
f69910b2fa
40
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
40
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
name: Bug report
|
||||
description: Something is not working as expected.
|
||||
title: Description of the bug
|
||||
labels: bug
|
||||
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: >-
|
||||
A clear and concise description of what the bug is.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: To Reproduce
|
||||
description: >-
|
||||
Steps to reproduce the behavior.
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: >-
|
||||
A clear and concise description of what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: >-
|
||||
Add any other context about the problem here.
|
36
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
36
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
name: Feature request
|
||||
description: Suggest an idea for this project
|
||||
title: Description of the feature request
|
||||
labels: enhancement
|
||||
|
||||
body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: Is your feature request related to a problem? Please describe.
|
||||
placeholder: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the solution you'd like
|
||||
placeholder: >-
|
||||
A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe alternatives you've considered
|
||||
placeholder: >-
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
placeholder: >-
|
||||
Add any other context or screenshots about the feature request here.
|
||||
validations:
|
||||
required: true
|
11
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
11
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
## Steps
|
||||
- [ ] Build the project on your machine. If it does not compile, fix the errors.
|
||||
- [ ] Describe the purpose and approach of your pull request below.
|
||||
- [ ] Submit the pull request. Thank you very much for your contribution!
|
||||
|
||||
## Purpose
|
||||
_Describe the problem or feature._
|
||||
_If there is a related issue, add the link._
|
||||
|
||||
## Approach
|
||||
_Describe how this pull request solves the problem or adds the feature._
|
45
.github/workflows/docs.yml
vendored
Normal file
45
.github/workflows/docs.yml
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
name: Deploy Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
Deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build Docs
|
||||
run: |
|
||||
xcrun xcodebuild docbuild \
|
||||
-scheme Meta \
|
||||
-destination 'generic/platform=macOS' \
|
||||
-derivedDataPath "$PWD/.derivedData" \
|
||||
-skipPackagePluginValidation
|
||||
xcrun docc process-archive transform-for-static-hosting \
|
||||
"$PWD/.derivedData/Build/Products/Debug/Meta.doccarchive" \
|
||||
--output-path "docs" \
|
||||
--hosting-base-path "Meta"
|
||||
- name: Modify Docs
|
||||
run: |
|
||||
echo "<script>window.location.href += \"/documentation/meta\"</script>" > docs/index.html;
|
||||
sed -i '' 's/,2px/,10px/g' docs/css/index.*.css
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: 'docs'
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
30
.github/workflows/swiftlint.yml
vendored
Normal file
30
.github/workflows/swiftlint.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
name: SwiftLint
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/swiftlint.yml'
|
||||
- '.swiftlint.yml'
|
||||
- '**/*.swift'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/swiftlint.yml'
|
||||
- '.swiftlint.yml'
|
||||
- '**/*.swift'
|
||||
workflow_dispatch:
|
||||
paths:
|
||||
- '.github/workflows/swiftlint.yml'
|
||||
- '.swiftlint.yml'
|
||||
- '**/*.swift'
|
||||
|
||||
jobs:
|
||||
SwiftLint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: SwiftLint
|
||||
uses: norio-nomura/action-swiftlint@3.2.1
|
||||
with:
|
||||
args: --strict
|
||||
env:
|
||||
WORKING_DIRECTORY: Source
|
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
||||
/Package.resolved
|
||||
.Ulysses-Group.plist
|
||||
/.docc-build
|
||||
/io.github.AparokshaUI.Generation.json
|
||||
/.vscode
|
163
.swiftlint.yml
Normal file
163
.swiftlint.yml
Normal file
@ -0,0 +1,163 @@
|
||||
# Opt-In Rules
|
||||
opt_in_rules:
|
||||
- anonymous_argument_in_multiline_closure
|
||||
- array_init
|
||||
- attributes
|
||||
- closure_body_length
|
||||
- closure_end_indentation
|
||||
- closure_spacing
|
||||
- collection_alignment
|
||||
- comma_inheritance
|
||||
- conditional_returns_on_newline
|
||||
- contains_over_filter_count
|
||||
- contains_over_filter_is_empty
|
||||
- contains_over_first_not_nil
|
||||
- contains_over_range_nil_comparison
|
||||
- convenience_type
|
||||
- discouraged_none_name
|
||||
- discouraged_object_literal
|
||||
- discouraged_optional_boolean
|
||||
- discouraged_optional_collection
|
||||
- empty_collection_literal
|
||||
- empty_count
|
||||
- empty_string
|
||||
- enum_case_associated_values_count
|
||||
- explicit_init
|
||||
- fallthrough
|
||||
- file_header
|
||||
- file_name
|
||||
- file_name_no_space
|
||||
- first_where
|
||||
- flatmap_over_map_reduce
|
||||
- force_unwrapping
|
||||
- function_default_parameter_at_end
|
||||
- identical_operands
|
||||
- implicit_return
|
||||
- implicitly_unwrapped_optional
|
||||
- joined_default_parameter
|
||||
- last_where
|
||||
- legacy_multiple
|
||||
- let_var_whitespace
|
||||
- literal_expression_end_indentation
|
||||
- local_doc_comment
|
||||
- lower_acl_than_parent
|
||||
- missing_docs
|
||||
- modifier_order
|
||||
- multiline_arguments
|
||||
- multiline_arguments_brackets
|
||||
- multiline_function_chains
|
||||
- multiline_literal_brackets
|
||||
- multiline_parameters
|
||||
- multiline_parameters_brackets
|
||||
- no_extension_access_modifier
|
||||
- no_grouping_extension
|
||||
- no_magic_numbers
|
||||
- number_separator
|
||||
- operator_usage_whitespace
|
||||
- optional_enum_case_matching
|
||||
- prefer_self_in_static_references
|
||||
- prefer_self_type_over_type_of_self
|
||||
- prefer_zero_over_explicit_init
|
||||
- prohibited_interface_builder
|
||||
- redundant_nil_coalescing
|
||||
- redundant_type_annotation
|
||||
- return_value_from_void_function
|
||||
- shorthand_optional_binding
|
||||
- sorted_first_last
|
||||
- sorted_imports
|
||||
- static_operator
|
||||
- strict_fileprivate
|
||||
- switch_case_on_newline
|
||||
- toggle_bool
|
||||
- trailing_closure
|
||||
- type_contents_order
|
||||
- unneeded_parentheses_in_closure_argument
|
||||
- yoda_condition
|
||||
|
||||
# Disabled Rules
|
||||
disabled_rules:
|
||||
- block_based_kvo
|
||||
- class_delegate_protocol
|
||||
- dynamic_inline
|
||||
- is_disjoint
|
||||
- no_fallthrough_only
|
||||
- notification_center_detachment
|
||||
- ns_number_init_as_function_reference
|
||||
- nsobject_prefer_isequal
|
||||
- private_over_fileprivate
|
||||
- redundant_objc_attribute
|
||||
- self_in_property_initialization
|
||||
- todo
|
||||
- unavailable_condition
|
||||
- valid_ibinspectable
|
||||
- xctfail_message
|
||||
|
||||
# Custom Rules
|
||||
custom_rules:
|
||||
github_issue:
|
||||
name: 'GitHub Issue'
|
||||
regex: '//.(TODO|FIXME):.(?!.*(https://github\.com/AparokshaUI/meta-sqlite/issues/\d))'
|
||||
message: 'The related GitHub issue must be included in a TODO or FIXME.'
|
||||
severity: warning
|
||||
|
||||
fatal_error:
|
||||
name: 'Fatal Error'
|
||||
regex: 'fatalError.*\(.*\)'
|
||||
message: 'Fatal error should not be used.'
|
||||
severity: error
|
||||
|
||||
enum_case_parameter:
|
||||
name: 'Enum Case Parameter'
|
||||
regex: 'case [a-zA-Z0-9]*\([a-zA-Z0-9\.<>?,\n\t =]+\)'
|
||||
message: 'The associated values of an enum case should have parameters.'
|
||||
severity: warning
|
||||
|
||||
tab:
|
||||
name: 'Whitespaces Instead of Tab'
|
||||
regex: '\t'
|
||||
message: 'Spaces should be used instead of tabs.'
|
||||
severity: warning
|
||||
|
||||
# Thanks to the creator of the SwiftLint rule
|
||||
# "empty_first_line"
|
||||
# https://github.com/coteditor/CotEditor/blob/main/.swiftlint.yml
|
||||
# in the GitHub repository
|
||||
# "CotEditor"
|
||||
# https://github.com/coteditor/CotEditor
|
||||
empty_first_line:
|
||||
name: 'Empty First Line'
|
||||
regex: '(^[ a-zA-Z ]*(?:protocol|extension|class|struct) (?!(?:var|let))[ a-zA-Z:]*\{\n *\S+)'
|
||||
message: 'There should be an empty line after a declaration'
|
||||
severity: error
|
||||
|
||||
# Analyzer Rules
|
||||
analyzer_rules:
|
||||
- unused_declaration
|
||||
- unused_import
|
||||
|
||||
# Options
|
||||
file_header:
|
||||
required_pattern: '(// swift-tools-version: .+)?//\n// .*.swift\n// meta-sqlite\n//\n// Created by .* on .*\.(\n// Edited by (.*,)+\.)*\n(\n// Thanks to .* for the .*:\n// ".*"\n// https://.* \(\d\d.\d\d.\d\d\))*//\n'
|
||||
missing_docs:
|
||||
warning: [internal, private]
|
||||
error: [open, public]
|
||||
excludes_inherited_types: false
|
||||
type_contents_order:
|
||||
order:
|
||||
- case
|
||||
- type_alias
|
||||
- associated_type
|
||||
- type_property
|
||||
- instance_property
|
||||
- ib_inspectable
|
||||
- ib_outlet
|
||||
- subscript
|
||||
- initializer
|
||||
- deinitializer
|
||||
- subtype
|
||||
- type_method
|
||||
- view_life_cycle_method
|
||||
- ib_action
|
||||
- other_method
|
||||
excluded:
|
||||
- .build/
|
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 david-swift
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
41
Package.swift
Normal file
41
Package.swift
Normal file
@ -0,0 +1,41 @@
|
||||
// swift-tools-version: 6.0
|
||||
//
|
||||
// Package.swift
|
||||
// meta-sqlite
|
||||
//
|
||||
// Created by david-swift on 04.10.24.
|
||||
//
|
||||
|
||||
import PackageDescription
|
||||
|
||||
/// The meta-sqlite package is part of the Aparoksha project.
|
||||
let package = Package(
|
||||
name: "meta-sqlite",
|
||||
platforms: [
|
||||
.macOS(.v10_15),
|
||||
.iOS(.v13)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "MetaSQLite",
|
||||
targets: ["MetaSQLite"]
|
||||
)
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/AparokshaUI/Meta", branch: "main"),
|
||||
.package(url: "https://github.com/stephencelis/SQLite.swift", from: "0.15.3")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "MetaSQLite",
|
||||
dependencies: ["Meta", .product(name: "SQLite", package: "SQLite.swift")],
|
||||
path: "Sources"
|
||||
),
|
||||
.executableTarget(
|
||||
name: "Tests",
|
||||
dependencies: ["MetaSQLite"],
|
||||
path: "Tests"
|
||||
)
|
||||
],
|
||||
swiftLanguageModes: [.v5]
|
||||
)
|
28
README.md
Normal file
28
README.md
Normal file
@ -0,0 +1,28 @@
|
||||
<p align="center">
|
||||
<h1 align="center">SQLite for Meta</h1>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://aparokshaui.github.io/meta-sqlite/">
|
||||
Documentation
|
||||
</a>
|
||||
·
|
||||
<a href="https://github.com/AparokshaUI/meta-sqlite">
|
||||
GitHub
|
||||
</a>
|
||||
</p>
|
||||
|
||||
_SQLite for Meta_ is a simple extension for the [Meta](https://aparokshaui.github.io/Meta) framework remembering state data between app launches.
|
||||
|
||||
## Thanks
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [Meta](https://github.com/AparokshaUI/Meta) licensed under the [MIT License](https://github.com/AparokshaUI/Meta/blob/main/LICENSE.md)
|
||||
- [SQLite.swift](https://github.com/stephencelis/SQLite.swift) licensed under the [MIT License](https://github.com/stephencelis/SQLite.swift/blob/master/LICENSE.txt)
|
||||
|
||||
### Other Thanks
|
||||
|
||||
- [DocC](https://github.com/apple/swift-docc) used for the documentation
|
||||
- [SwiftLint](https://github.com/realm/SwiftLint) for checking whether code style conventions are violated
|
||||
- The programming language [Swift](https://github.com/swiftlang/swift)
|
93
Sources/State.swift
Normal file
93
Sources/State.swift
Normal file
@ -0,0 +1,93 @@
|
||||
//
|
||||
// State.swift
|
||||
// meta-sqlite
|
||||
//
|
||||
// Created by david-swift on 04.10.24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Meta
|
||||
import SQLite
|
||||
|
||||
extension State where Value: Codable {
|
||||
|
||||
/// Initialize a property being remembered between launches of the app.
|
||||
/// - Parameters:
|
||||
/// - wrappedValue: The wrapped value, used as an initial value if no stored value is found.
|
||||
/// - stateID: The identifier for the stored value.
|
||||
/// - forceUpdates: Whether to force update all available views when the property gets modified.
|
||||
///
|
||||
/// Define a custom path for storing the data with ``DatabaseInformation/setPath(_:)``.
|
||||
public init(wrappedValue: @autoclosure @escaping () -> Value, _ stateID: String, forceUpdates: Bool = false) {
|
||||
self.init(
|
||||
wrappedValue: {
|
||||
let query = DatabaseInformation.table
|
||||
.filter(DatabaseInformation.id == stateID)
|
||||
.limit(1)
|
||||
var data: Data?
|
||||
guard let rows = try? DatabaseInformation.connection?.prepare(query) else {
|
||||
return wrappedValue()
|
||||
}
|
||||
for row in rows {
|
||||
data = row[DatabaseInformation.data]
|
||||
}
|
||||
guard let data else {
|
||||
return wrappedValue()
|
||||
}
|
||||
let value = try? JSONDecoder().decode(Value.self, from: data)
|
||||
return value ?? wrappedValue()
|
||||
},
|
||||
writeValue: { value in
|
||||
if let data = try? JSONEncoder().encode(value) {
|
||||
_ = try? DatabaseInformation.connection?.run(
|
||||
DatabaseInformation.table.insert(
|
||||
or: .replace,
|
||||
DatabaseInformation.id <- stateID,
|
||||
DatabaseInformation.data <- data
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
forceUpdates: forceUpdates
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Information about the database.
|
||||
public enum DatabaseInformation {
|
||||
|
||||
/// The path.
|
||||
static var path = "./database.sqlite"
|
||||
/// The table.
|
||||
static let table = Table("data")
|
||||
/// The ID field.
|
||||
static let id = SQLite.Expression<String>("id")
|
||||
/// The data field.
|
||||
static let data = SQLite.Expression<Data>("data")
|
||||
/// The SQLite connection.
|
||||
private static var privateConnection: Connection?
|
||||
|
||||
/// The SQLite connection.
|
||||
static var connection: Connection? {
|
||||
if let privateConnection {
|
||||
return privateConnection
|
||||
}
|
||||
let connection = try? Connection(path)
|
||||
privateConnection = connection
|
||||
_ = try? connection?.run(table.create { table in
|
||||
table.column(id, primaryKey: true)
|
||||
table.column(data)
|
||||
})
|
||||
return connection
|
||||
}
|
||||
|
||||
/// Set the path to the SQLite file.
|
||||
/// - Parameter path: The path.
|
||||
///
|
||||
/// Call this function before accessing any state values (when setting up the app storage).
|
||||
public static func setPath(_ path: String) {
|
||||
self.path = path
|
||||
}
|
||||
|
||||
}
|
4
Sources/meta-sqlite.docc/MetaSQLite.md
Normal file
4
Sources/meta-sqlite.docc/MetaSQLite.md
Normal file
@ -0,0 +1,4 @@
|
||||
# ``MetaSQLite``
|
||||
|
||||
_SQLite for Meta_ is a simple extension for the [Meta](https://aparokshaui.github.io/Meta) framework remembering state data between app launches.
|
||||
|
45
Sources/meta-sqlite.docc/theme-settings.json
Normal file
45
Sources/meta-sqlite.docc/theme-settings.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"theme": {
|
||||
"border-radius": "10px",
|
||||
"button": {
|
||||
"border-radius": "20px"
|
||||
},
|
||||
"color": {
|
||||
"button-background": "#ea3358",
|
||||
"button-background-active": "#ea3358",
|
||||
"button-background-hover": "#fc557a",
|
||||
"button-text": "#ffffff",
|
||||
"header": "#7f313b",
|
||||
"documentation-intro-accent": "var(--color-header)",
|
||||
"link": "#ea3358",
|
||||
"nav-link-color": "#ea3358",
|
||||
"nav-dark-link-color": "#ea3358",
|
||||
"tutorials-overview-link": "#fb4469",
|
||||
"step-background": {
|
||||
"light": "#fffaff",
|
||||
"dark": "#302c2d"
|
||||
},
|
||||
"step-focused": "#ea3358",
|
||||
"tabnav-item-border-color": "#ea3358",
|
||||
"tutorial-background": {
|
||||
"light": "",
|
||||
"dark": "#1d1d1f"
|
||||
},
|
||||
"tutorials-overview-background": "linear-gradient(180deg, rgba(43,20,23,1) 0%, rgba(41, 3, 8, 0.808) 60%, rgba(0,0,0,1) 100%)",
|
||||
"fill-light-blue-secondary": "#ea3358",
|
||||
"fill-blue": "#ea3358",
|
||||
"figure-blue": "#ea3358",
|
||||
"standard-blue-documentation-intro-fill": "#ea3358",
|
||||
"figure-blue": "#ea3358",
|
||||
"tutorial-hero-background": "#100a0b",
|
||||
"navigator-item-hover": {
|
||||
"light": "#ea335815",
|
||||
"dark": "#7f313b"
|
||||
}
|
||||
},
|
||||
"additionalProperties": "#ea3358",
|
||||
"tutorial-step": {
|
||||
"border-radius": "15px"
|
||||
}
|
||||
}
|
||||
}
|
23
Tests/Test.swift
Normal file
23
Tests/Test.swift
Normal file
@ -0,0 +1,23 @@
|
||||
//
|
||||
// Test.swift
|
||||
// meta-sqlite
|
||||
//
|
||||
// Created by david-swift on 04.10.24.
|
||||
//
|
||||
|
||||
import Meta
|
||||
import MetaSQLite
|
||||
|
||||
/// The test function.
|
||||
func main() {
|
||||
@State("test")
|
||||
var test = 0
|
||||
@State("other")
|
||||
var other = 3
|
||||
print(test)
|
||||
print(other)
|
||||
test = 1
|
||||
other = 0
|
||||
}
|
||||
|
||||
main()
|
Loading…
Reference in New Issue
Block a user