Я пытаюсь использовать модуль FamilyControls, чтобы ограничить время работы приложений. Ограничитель работает правильно, но я хочу видеть названия приложений, которые ограничены, в моем приложении Flutter. Я перепробовал все, но не могу понять, как получить идентификаторы пакетов этих приложений.
Я знаю, что это возможно, потому что существуют блокировщики приложений, которые это делают.
for (index, token) in applications.applicationTokens.enumerated() {
NSLog("Debug: test 1: \(applications)")
NSLog("Debug: test 2: \(type(of: applications.applicationTokens.enumerated()))")
NSLog("Debug: index: \(index)")
NSLog("Debug: token: \(token)")
}
Этот код дает следующий результат:
Отладка: тест 1: FamilyActivitySelection(includeEntireCategory: false, applicationTokens: Set([ApplicationToken(data: 128) байты: `D^R'įÅyñxnÆøH4sr;Øý³¶ï(DA^_ÒÖÜÄ~
Å^A¨u^O^ÎÕ^WÆG.aBÍEiÿ_n!jÜj¾U^Hi¼ý^[,H2v7cî·(o[Þò¤¤Þ^[ã/^Ní;ÿÙL^P=Ý ^]®+>¹· Á)]), категорияТокены: Set([]), webDomainTokens: Set([]), unkenizedApplicationIdentifiers: Set([]), unkenizedCategoryIdentifiers: Set([]), unkenizedWebDomainIdentifiers: Set([]))
Отладка: тест 2: EnumeratedSequence< Set>
Отладка: индекс: 0
Отладка: токен: ApplicationToken(данные: 128 байт)
Вот часть моего кода (я добавил много, потому что понятия не имею, что происходит):
DeviceActivityMonitorExtension.swift:
typealias ApplicationToken = ManagedSettings.Token
typealias ActivityCategoryToken = ManagedSettings.Token
class DeviceActivityMonitorExtension: DeviceActivityMonitor {
override func intervalDidStart(for activity: DeviceActivityName) {
super.intervalDidStart(for: activity)
}
override func intervalDidEnd(for activity: DeviceActivityName) {
super.intervalDidEnd(for: activity)
}
override func eventDidReachThreshold(_ event: DeviceActivityEvent.Name, activity: DeviceActivityName) {
super.eventDidReachThreshold(event, activity: activity)
NSLog("Threshold reached for event: \(event.rawValue)")
// Retrieve stored tokens from shared UserDefaults
guard let defaults = UserDefaults(suiteName: appGroupID) else {
NSLog("No app group defaults available.")
return
}
let decoder = JSONDecoder()
var applicationTokens: [ApplicationToken] = []
var categoryTokens: [ActivityCategoryToken] = []
// Decode application tokens from the new key
if let appTokensData = defaults.data(forKey: Constants.limitedApplicationTokensKey) {
if let tokens = try? decoder.decode([ApplicationToken].self, from: appTokensData) {
applicationTokens = tokens
NSLog("Decoded \(tokens.count) application tokens.")
} else {
NSLog("Failed to decode application tokens.")
}
} else {
NSLog("No application tokens found in UserDefaults.")
}
// Decode category tokens if available
if let catTokensData = defaults.data(forKey: Constants.limitedCategoryIdentifiersKey) {
if let cats = try? decoder.decode([ActivityCategoryToken].self, from: catTokensData) {
categoryTokens = cats
NSLog("Decoded \(cats.count) category tokens.")
} else {
NSLog("Failed to decode category tokens.")
}
} else {
NSLog("No category tokens found in UserDefaults.")
}
// Apply restrictions now that the threshold is reached
let store = ManagedSettingsStore()
// If we have no application tokens, set nil; otherwise, set them as a Set
store.shield.applications = applicationTokens.isEmpty ? nil : Set(applicationTokens)
// For categories, we use a specific policy if we have any
store.shield.applicationCategories = categoryTokens.isEmpty
? nil
: ShieldSettings.ActivityCategoryPolicy.specific(Set(categoryTokens))
NSLog("Restrictions applied. The selected apps and categories should now show the Restricted screen.")
}
override func intervalWillStartWarning(for activity: DeviceActivityName) {
super.intervalWillStartWarning(for: activity)
}
override func intervalWillEndWarning(for activity: DeviceActivityName) {
super.intervalWillEndWarning(for: activity)
}
override func eventWillReachThresholdWarning(_ event: DeviceActivityEvent.Name, activity: DeviceActivityName) {
super.eventWillReachThresholdWarning(event, activity: activity)
}
}
Моямодель:
import Foundation
import FamilyControls
import ManagedSettings
private let _MyModel = MyModel()
class MyModel: ObservableObject {
let store = ManagedSettingsStore()
@Published var selectionToDiscourage: FamilyActivitySelection
@Published var selectionToEncourage: FamilyActivitySelection
@Published var selectionToLimit: FamilyActivitySelection
init() {
selectionToDiscourage = FamilyActivitySelection()
selectionToEncourage = FamilyActivitySelection()
selectionToLimit = FamilyActivitySelection()
}
class var shared: MyModel {
return _MyModel
}
func setShieldRestrictions() {
NSLog("Debug: Setting shield restrictions for discouraged apps: \(selectionToDiscourage.applicationTokens)")
let applications = self.selectionToDiscourage
store.shield.applications = applications.applicationTokens.isEmpty ? nil : applications.applicationTokens
store.shield.applicationCategories = applications.categoryTokens.isEmpty
? nil
: ShieldSettings.ActivityCategoryPolicy.specific(applications.categoryTokens)
}
// Used to encode codable to UserDefaults
private let encoder = PropertyListEncoder()
// Used to decode codable from UserDefaults
private let decoder = PropertyListDecoder()
func saveFamilyActivitySelection(selection: FamilyActivitySelection) {
NSLog("Debug: selected app updated: ", selection.applicationTokens.count," category: ", selection.categoryTokens.count)
let defaults = UserDefaults.standard
defaults.set(
try? encoder.encode(selection),
forKey: "limitedApplicationTokens"
)
//check is data saved to user defaults
getSavedFamilyActivitySelection()
}
//get saved family activity selection from UserDefault
func getSavedFamilyActivitySelection() -> FamilyActivitySelection? {
let defaults = UserDefaults.standard
guard let data = defaults.data(forKey: "limitedApplicationTokens") else {
return nil
}
var selectedApp: FamilyActivitySelection?
let decoder = PropertyListDecoder()
selectedApp = try? decoder.decode(FamilyActivitySelection.self, from: data)
NSLog("Debug: saved selected app updated: ", selectedApp?.categoryTokens.count ?? "0")
return selectedApp
}
}
AppDelegate.swift:
func clearLimitedAppsData() {
guard let defaults = UserDefaults(suiteName: Constants.appGroupID) else { return }
defaults.removeObject(forKey: Constants.limitedApplicationBundleIdentifiersKey)
defaults.synchronize()
NSLog("Debug: Cleared limitedApplicationBundleIdentifiersKey data.")
}
var globalMethodCall = ""
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// clearLimitedAppsData()
guard let controller = window?.rootViewController as? FlutterViewController else {
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
let METHOD_CHANNEL_NAME = "flutter_screentime"
let model = MyModel.shared
let store = ManagedSettingsStore()
let methodChannel = FlutterMethodChannel(name: METHOD_CHANNEL_NAME, binaryMessenger: controller.binaryMessenger)
methodChannel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in
Task {
// Request authorization for Family Controls if available
if #available(iOS 16.0, *) {
do {
try await AuthorizationCenter.shared.requestAuthorization(for: .individual)
NSLog("Debug: Authorization request succeeded.")
} catch {
NSLog("Debug: Authorization request failed with error: \(error)")
result(FlutterError(code: "AUTH_ERROR", message: "Authorization failed", details: nil))
return
}
} else {
NSLog("Debug: iOS version is below 16.0, cannot proceed.")
result(FlutterError(code: "iOS_VERSION_ERROR", message: "iOS 16.0 or newer is required", details: nil))
return
}
switch call.method {
case "blockApp":
globalMethodCall = "selectAppsToDiscourage"
NSLog("Debug: Presenting blockApp picker.")
let vc = UIHostingController(rootView: ContentView()
.environmentObject(model)
.environmentObject(store))
controller.present(vc, animated: true, completion: nil)
result("blockApp invoked")
case "unblockApp":
globalMethodCall = "selectAppsToEncourage"
NSLog("Debug: Presenting unblockApp picker.")
let vc = UIHostingController(rootView: ContentView()
.environmentObject(model)
.environmentObject(store))
controller.present(vc, animated: true, completion: nil)
result("unblockApp invoked")
case "limitApp":
globalMethodCall = "selectAppsToLimit"
NSLog("Debug: Presenting limitApp picker.")
let vc = UIHostingController(rootView: ContentView()
.environmentObject(model)
.environmentObject(store))
controller.present(vc, animated: true, completion: nil)
result("limitApp invoked")
case "getLimitedApps":
if #available(iOS 16.0, *) {
let limitedApps = self.decodeLimitedApps()
NSLog("Debug: Returning limited apps: \(limitedApps)")
result(limitedApps)
} else {
NSLog("Debug: iOS < 16.0, cannot get limited apps.")
result(["No limited apps or iOS < 16.0"])
}
default:
NSLog("Debug: Unhandled method call: \(call.method)")
result(FlutterMethodNotImplemented)
}
}
}
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
@available(iOS 16.0, *)
func decodeLimitedApps() -> [String] {
guard let defaults = UserDefaults(suiteName: Constants.appGroupID) else {
NSLog("Debug: No app group defaults available.")
return ["No limited apps found."]
}
let decoder = JSONDecoder()
var limitedApps: [String] = []
NSLog("Debug: Attempting to read limited apps from UserDefaults.")
if let appBundleIDsData = defaults.data(forKey: Constants.limitedApplicationBundleIdentifiersKey) {
NSLog("Debug: Found app bundle IDs data: \(appBundleIDsData.count) bytes")
// Decode the data as [String]
do {
let bundleIDs = try decoder.decode([String].self, from: appBundleIDsData)
if !bundleIDs.isEmpty {
NSLog("Debug: Decoded bundle IDs: \(bundleIDs)")
limitedApps = bundleIDs
for bundleID in bundleIDs {
NSLog("Bundle Identifier: \(bundleID)")
}
} else {
NSLog("Debug: Decoded bundle IDs array is empty.")
}
} catch {
NSLog("Debug: Failed to decode bundle IDs: \(error.localizedDescription)")
// Optionally, log the raw data as a string for inspection
if let rawString = String(data: appBundleIDsData, encoding: .utf8) {
NSLog("Debug: Raw appBundleIDsData as String: \(rawString)")
}
}
} else {
NSLog("Debug: No app bundle IDs data found in UserDefaults.")
}
if limitedApps.isEmpty {
return ["No limited apps found!"]
}
return limitedApps
}
func storeLimitedApps(bundleIDs: [String], categoryIDs: [String]) {
guard let defaults = UserDefaults(suiteName: Constants.appGroupID) else {
NSLog("Debug: Failed to access UserDefaults with App Group ID.")
return
}
let encoder = JSONEncoder()
do {
let appData = try encoder.encode(bundleIDs)
defaults.set(appData, forKey: Constants.limitedApplicationBundleIdentifiersKey)
NSLog("Debug: Stored \(bundleIDs.count) application bundle identifiers: \(bundleIDs)")
} catch {
NSLog("Debug: Failed to encode application bundle identifiers: \(error.localizedDescription)")
}
// Similarly handle categoryIDs if necessary
do {
let catData = try encoder.encode(categoryIDs)
defaults.set(catData, forKey: Constants.limitedCategoryIdentifiersKey)
NSLog("Debug: Stored \(categoryIDs.count) category identifiers.")
} catch {
NSLog("Debug: Failed to encode category identifiers: \(error.localizedDescription)")
}
defaults.synchronize()
}
}
Подробнее здесь: https://stackoverflow.com/questions/792 ... ame-of-app
Swift использует Семейный контроль, чтобы ограничить приложения и получить название приложения ⇐ IOS
Программируем под IOS
1734389344
Anonymous
Я пытаюсь использовать модуль FamilyControls, чтобы ограничить время работы приложений. Ограничитель работает правильно, но я хочу видеть названия приложений, которые ограничены, в моем приложении Flutter. Я перепробовал все, но не могу понять, как получить идентификаторы пакетов этих приложений.
Я знаю, что это возможно, потому что существуют блокировщики приложений, которые это делают.
for (index, token) in applications.applicationTokens.enumerated() {
NSLog("Debug: test 1: \(applications)")
NSLog("Debug: test 2: \(type(of: applications.applicationTokens.enumerated()))")
NSLog("Debug: index: \(index)")
NSLog("Debug: token: \(token)")
}
Этот код дает следующий результат:
Отладка: тест 1: FamilyActivitySelection(includeEntireCategory: false, applicationTokens: Set([ApplicationToken(data: 128) байты: `D^R'įÅyñxnÆøH4sr;Øý³¶ï(DA^_ÒÖÜÄ~
Å^A¨u^O^ÎÕ^WÆG.aBÍEiÿ_n!jÜj¾U^Hi¼ý^[,H2v7cî·(o[Þò¤¤Þ^[ã/^Ní;ÿÙL^P=Ý ^]®+>¹· Á)]), категорияТокены: Set([]), webDomainTokens: Set([]), unkenizedApplicationIdentifiers: Set([]), unkenizedCategoryIdentifiers: Set([]), unkenizedWebDomainIdentifiers: Set([]))
Отладка: тест 2: EnumeratedSequence< Set>
Отладка: индекс: 0
Отладка: токен: ApplicationToken(данные: 128 байт)
Вот часть моего кода (я добавил много, потому что понятия не имею, что происходит):
DeviceActivityMonitorExtension.swift:
typealias ApplicationToken = ManagedSettings.Token
typealias ActivityCategoryToken = ManagedSettings.Token
class DeviceActivityMonitorExtension: DeviceActivityMonitor {
override func intervalDidStart(for activity: DeviceActivityName) {
super.intervalDidStart(for: activity)
}
override func intervalDidEnd(for activity: DeviceActivityName) {
super.intervalDidEnd(for: activity)
}
override func eventDidReachThreshold(_ event: DeviceActivityEvent.Name, activity: DeviceActivityName) {
super.eventDidReachThreshold(event, activity: activity)
NSLog("Threshold reached for event: \(event.rawValue)")
// Retrieve stored tokens from shared UserDefaults
guard let defaults = UserDefaults(suiteName: appGroupID) else {
NSLog("No app group defaults available.")
return
}
let decoder = JSONDecoder()
var applicationTokens: [ApplicationToken] = []
var categoryTokens: [ActivityCategoryToken] = []
// Decode application tokens from the new key
if let appTokensData = defaults.data(forKey: Constants.limitedApplicationTokensKey) {
if let tokens = try? decoder.decode([ApplicationToken].self, from: appTokensData) {
applicationTokens = tokens
NSLog("Decoded \(tokens.count) application tokens.")
} else {
NSLog("Failed to decode application tokens.")
}
} else {
NSLog("No application tokens found in UserDefaults.")
}
// Decode category tokens if available
if let catTokensData = defaults.data(forKey: Constants.limitedCategoryIdentifiersKey) {
if let cats = try? decoder.decode([ActivityCategoryToken].self, from: catTokensData) {
categoryTokens = cats
NSLog("Decoded \(cats.count) category tokens.")
} else {
NSLog("Failed to decode category tokens.")
}
} else {
NSLog("No category tokens found in UserDefaults.")
}
// Apply restrictions now that the threshold is reached
let store = ManagedSettingsStore()
// If we have no application tokens, set nil; otherwise, set them as a Set
store.shield.applications = applicationTokens.isEmpty ? nil : Set(applicationTokens)
// For categories, we use a specific policy if we have any
store.shield.applicationCategories = categoryTokens.isEmpty
? nil
: ShieldSettings.ActivityCategoryPolicy.specific(Set(categoryTokens))
NSLog("Restrictions applied. The selected apps and categories should now show the Restricted screen.")
}
override func intervalWillStartWarning(for activity: DeviceActivityName) {
super.intervalWillStartWarning(for: activity)
}
override func intervalWillEndWarning(for activity: DeviceActivityName) {
super.intervalWillEndWarning(for: activity)
}
override func eventWillReachThresholdWarning(_ event: DeviceActivityEvent.Name, activity: DeviceActivityName) {
super.eventWillReachThresholdWarning(event, activity: activity)
}
}
Моямодель:
import Foundation
import FamilyControls
import ManagedSettings
private let _MyModel = MyModel()
class MyModel: ObservableObject {
let store = ManagedSettingsStore()
@Published var selectionToDiscourage: FamilyActivitySelection
@Published var selectionToEncourage: FamilyActivitySelection
@Published var selectionToLimit: FamilyActivitySelection
init() {
selectionToDiscourage = FamilyActivitySelection()
selectionToEncourage = FamilyActivitySelection()
selectionToLimit = FamilyActivitySelection()
}
class var shared: MyModel {
return _MyModel
}
func setShieldRestrictions() {
NSLog("Debug: Setting shield restrictions for discouraged apps: \(selectionToDiscourage.applicationTokens)")
let applications = self.selectionToDiscourage
store.shield.applications = applications.applicationTokens.isEmpty ? nil : applications.applicationTokens
store.shield.applicationCategories = applications.categoryTokens.isEmpty
? nil
: ShieldSettings.ActivityCategoryPolicy.specific(applications.categoryTokens)
}
// Used to encode codable to UserDefaults
private let encoder = PropertyListEncoder()
// Used to decode codable from UserDefaults
private let decoder = PropertyListDecoder()
func saveFamilyActivitySelection(selection: FamilyActivitySelection) {
NSLog("Debug: selected app updated: ", selection.applicationTokens.count," category: ", selection.categoryTokens.count)
let defaults = UserDefaults.standard
defaults.set(
try? encoder.encode(selection),
forKey: "limitedApplicationTokens"
)
//check is data saved to user defaults
getSavedFamilyActivitySelection()
}
//get saved family activity selection from UserDefault
func getSavedFamilyActivitySelection() -> FamilyActivitySelection? {
let defaults = UserDefaults.standard
guard let data = defaults.data(forKey: "limitedApplicationTokens") else {
return nil
}
var selectedApp: FamilyActivitySelection?
let decoder = PropertyListDecoder()
selectedApp = try? decoder.decode(FamilyActivitySelection.self, from: data)
NSLog("Debug: saved selected app updated: ", selectedApp?.categoryTokens.count ?? "0")
return selectedApp
}
}
AppDelegate.swift:
func clearLimitedAppsData() {
guard let defaults = UserDefaults(suiteName: Constants.appGroupID) else { return }
defaults.removeObject(forKey: Constants.limitedApplicationBundleIdentifiersKey)
defaults.synchronize()
NSLog("Debug: Cleared limitedApplicationBundleIdentifiersKey data.")
}
var globalMethodCall = ""
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// clearLimitedAppsData()
guard let controller = window?.rootViewController as? FlutterViewController else {
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
let METHOD_CHANNEL_NAME = "flutter_screentime"
let model = MyModel.shared
let store = ManagedSettingsStore()
let methodChannel = FlutterMethodChannel(name: METHOD_CHANNEL_NAME, binaryMessenger: controller.binaryMessenger)
methodChannel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in
Task {
// Request authorization for Family Controls if available
if #available(iOS 16.0, *) {
do {
try await AuthorizationCenter.shared.requestAuthorization(for: .individual)
NSLog("Debug: Authorization request succeeded.")
} catch {
NSLog("Debug: Authorization request failed with error: \(error)")
result(FlutterError(code: "AUTH_ERROR", message: "Authorization failed", details: nil))
return
}
} else {
NSLog("Debug: iOS version is below 16.0, cannot proceed.")
result(FlutterError(code: "iOS_VERSION_ERROR", message: "iOS 16.0 or newer is required", details: nil))
return
}
switch call.method {
case "blockApp":
globalMethodCall = "selectAppsToDiscourage"
NSLog("Debug: Presenting blockApp picker.")
let vc = UIHostingController(rootView: ContentView()
.environmentObject(model)
.environmentObject(store))
controller.present(vc, animated: true, completion: nil)
result("blockApp invoked")
case "unblockApp":
globalMethodCall = "selectAppsToEncourage"
NSLog("Debug: Presenting unblockApp picker.")
let vc = UIHostingController(rootView: ContentView()
.environmentObject(model)
.environmentObject(store))
controller.present(vc, animated: true, completion: nil)
result("unblockApp invoked")
case "limitApp":
globalMethodCall = "selectAppsToLimit"
NSLog("Debug: Presenting limitApp picker.")
let vc = UIHostingController(rootView: ContentView()
.environmentObject(model)
.environmentObject(store))
controller.present(vc, animated: true, completion: nil)
result("limitApp invoked")
case "getLimitedApps":
if #available(iOS 16.0, *) {
let limitedApps = self.decodeLimitedApps()
NSLog("Debug: Returning limited apps: \(limitedApps)")
result(limitedApps)
} else {
NSLog("Debug: iOS < 16.0, cannot get limited apps.")
result(["No limited apps or iOS < 16.0"])
}
default:
NSLog("Debug: Unhandled method call: \(call.method)")
result(FlutterMethodNotImplemented)
}
}
}
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
@available(iOS 16.0, *)
func decodeLimitedApps() -> [String] {
guard let defaults = UserDefaults(suiteName: Constants.appGroupID) else {
NSLog("Debug: No app group defaults available.")
return ["No limited apps found."]
}
let decoder = JSONDecoder()
var limitedApps: [String] = []
NSLog("Debug: Attempting to read limited apps from UserDefaults.")
if let appBundleIDsData = defaults.data(forKey: Constants.limitedApplicationBundleIdentifiersKey) {
NSLog("Debug: Found app bundle IDs data: \(appBundleIDsData.count) bytes")
// Decode the data as [String]
do {
let bundleIDs = try decoder.decode([String].self, from: appBundleIDsData)
if !bundleIDs.isEmpty {
NSLog("Debug: Decoded bundle IDs: \(bundleIDs)")
limitedApps = bundleIDs
for bundleID in bundleIDs {
NSLog("Bundle Identifier: \(bundleID)")
}
} else {
NSLog("Debug: Decoded bundle IDs array is empty.")
}
} catch {
NSLog("Debug: Failed to decode bundle IDs: \(error.localizedDescription)")
// Optionally, log the raw data as a string for inspection
if let rawString = String(data: appBundleIDsData, encoding: .utf8) {
NSLog("Debug: Raw appBundleIDsData as String: \(rawString)")
}
}
} else {
NSLog("Debug: No app bundle IDs data found in UserDefaults.")
}
if limitedApps.isEmpty {
return ["No limited apps found!"]
}
return limitedApps
}
func storeLimitedApps(bundleIDs: [String], categoryIDs: [String]) {
guard let defaults = UserDefaults(suiteName: Constants.appGroupID) else {
NSLog("Debug: Failed to access UserDefaults with App Group ID.")
return
}
let encoder = JSONEncoder()
do {
let appData = try encoder.encode(bundleIDs)
defaults.set(appData, forKey: Constants.limitedApplicationBundleIdentifiersKey)
NSLog("Debug: Stored \(bundleIDs.count) application bundle identifiers: \(bundleIDs)")
} catch {
NSLog("Debug: Failed to encode application bundle identifiers: \(error.localizedDescription)")
}
// Similarly handle categoryIDs if necessary
do {
let catData = try encoder.encode(categoryIDs)
defaults.set(catData, forKey: Constants.limitedCategoryIdentifiersKey)
NSLog("Debug: Stored \(categoryIDs.count) category identifiers.")
} catch {
NSLog("Debug: Failed to encode category identifiers: \(error.localizedDescription)")
}
defaults.synchronize()
}
}
Подробнее здесь: [url]https://stackoverflow.com/questions/79286261/swift-using-family-controls-to-limit-apps-and-get-name-of-app[/url]
Ответить
1 сообщение
• Страница 1 из 1
Перейти
- Кемерово-IT
- ↳ Javascript
- ↳ C#
- ↳ JAVA
- ↳ Elasticsearch aggregation
- ↳ Python
- ↳ Php
- ↳ Android
- ↳ Html
- ↳ Jquery
- ↳ C++
- ↳ IOS
- ↳ CSS
- ↳ Excel
- ↳ Linux
- ↳ Apache
- ↳ MySql
- Детский мир
- Для души
- ↳ Музыкальные инструменты даром
- ↳ Печатная продукция даром
- Внешняя красота и здоровье
- ↳ Одежда и обувь для взрослых даром
- ↳ Товары для здоровья
- ↳ Физкультура и спорт
- Техника - даром!
- ↳ Автомобилистам
- ↳ Компьютерная техника
- ↳ Плиты: газовые и электрические
- ↳ Холодильники
- ↳ Стиральные машины
- ↳ Телевизоры
- ↳ Телефоны, смартфоны, плашеты
- ↳ Швейные машинки
- ↳ Прочая электроника и техника
- ↳ Фототехника
- Ремонт и интерьер
- ↳ Стройматериалы, инструмент
- ↳ Мебель и предметы интерьера даром
- ↳ Cантехника
- Другие темы
- ↳ Разное даром
- ↳ Давай меняться!
- ↳ Отдам\возьму за копеечку
- ↳ Работа и подработка в Кемерове
- ↳ Давай с тобой поговорим...
Мобильная версия