Skip to content

Commit 3b06d7a

Browse files
committed
Pin tracker feature added
1 parent 665c72f commit 3b06d7a

File tree

10 files changed

+112
-24
lines changed

10 files changed

+112
-24
lines changed

TrackerApp/CoreData/Model.xcdatamodeld/Model.xcdatamodel/contents

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@
88
<entity name="TrackerCoreData" representedClassName="TrackerCoreData" syncable="YES" codeGenerationType="class">
99
<attribute name="colorHex" attributeType="String"/>
1010
<attribute name="emoji" attributeType="String"/>
11+
<attribute name="initialCategory" attributeType="String"/>
1112
<attribute name="name" attributeType="String"/>
1213
<attribute name="scheduleString" attributeType="String"/>
1314
<attribute name="trackerID" attributeType="String"/>
1415
<relationship name="category" maxCount="1" deletionRule="Nullify" destinationEntity="TrackerCategoryCoreData" inverseName="trackers" inverseEntity="TrackerCategoryCoreData"/>
15-
<relationship name="records" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="TrackerRecordCoreData" inverseName="tracker" inverseEntity="TrackerRecordCoreData"/>
16+
<relationship name="records" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="TrackerRecordCoreData" inverseName="tracker" inverseEntity="TrackerRecordCoreData"/>
1617
</entity>
1718
<entity name="TrackerRecordCoreData" representedClassName="TrackerRecordCoreData" syncable="YES" codeGenerationType="class">
1819
<attribute name="date" attributeType="Date" usesScalarValueType="NO"/>

TrackerApp/Resources/Localization.swift

+4
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,12 @@ enum L10n {
4242
static let categoryNamePlaceholder = NSLocalizedString("categoryNamePlaceholder", comment: "Category name placeholder")
4343
static let doneTitle = NSLocalizedString("doneTitle", comment: "Done button title")
4444
static let pinTitle = NSLocalizedString("pinTitle", comment: "Pin tracker title")
45+
static let unpinTitle = NSLocalizedString("unpinTitle", comment: "Unpin tracker title")
4546
static let editTitle = NSLocalizedString("editTitle", comment: "Edit tracker title")
4647
static let deleteTitle = NSLocalizedString("deleteTitle", comment: "Delete tracker title")
48+
static let saveTitle = NSLocalizedString("saveTitle", comment: "Save button title")
49+
static let deleteConfirmationTitle = NSLocalizedString("deleteConfirmationTitle", comment: "Delete confirm title")
50+
static let pinnedTitle = NSLocalizedString("pinnedTitle", comment: "Pinned trackers category title")
4751
}
4852
enum Weekdays {
4953
// Full style

TrackerApp/Resources/en.lproj/Localizable.strings

+4
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,9 @@ saturdayShortTitle = "Sa";
4949
sundayShortTitle = "Su";
5050
everyDayTitle = "Every day";
5151
pinTitle = "Pin";
52+
unpinTitle = "Unpin";
5253
editTitle = "Edit";
5354
deleteTitle = "Delete";
55+
saveTitle = "Save";
56+
deleteConfirmationTitle = "Are you sure you want to delete tracker?";
57+
pinnedTitle = "\U2009Pinned";

TrackerApp/Resources/ru.lproj/Localizable.strings

+4
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,9 @@ saturdayShortTitle = "Сб";
4949
sundayShortTitle = "Вс";
5050
everyDayTitle = "Каждый день";
5151
pinTitle = "Закрепить";
52+
unpinTitle = "Открепить";
5253
editTitle = "Редактировать";
5354
deleteTitle = "Удалить";
55+
saveTitle = "Сохранить";
56+
deleteConfirmationTitle = "Уверены что хотите удалить трекер?";
57+
pinnedTitle = "\U2009Закрепленные";

TrackerApp/Services/TrackerStore/TrackerStore.swift

+41-4
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@ final class TrackerStore: NSObject {
2121

2222
private lazy var fetchedResultsController: NSFetchedResultsController<TrackerCoreData> = {
2323
let fetchRequest = TrackerCoreData.fetchRequest()
24-
fetchRequest.sortDescriptors = [NSSortDescriptor(key: Constants.sortTrackersByCategoryKey, ascending: true),
25-
NSSortDescriptor(key: Constants.sortTrackersByNameKey, ascending: true)]
24+
fetchRequest.sortDescriptors = [NSSortDescriptor(key: Constants.sortTrackersByCategoryKey,
25+
ascending: true,
26+
selector: #selector(NSString.localizedStandardCompare(_:))),
27+
NSSortDescriptor(key: Constants.sortTrackersByNameKey,
28+
ascending: true,
29+
selector: #selector(NSString.localizedStandardCompare(_:)))]
2630
fetchRequest.predicate = NSPredicate(format: "%K CONTAINS[n] %@", #keyPath(TrackerCoreData.scheduleString), "\(Date().startOfDay.weekDayString)")
2731
let controller = NSFetchedResultsController(fetchRequest: fetchRequest,
2832
managedObjectContext: context,
@@ -182,14 +186,20 @@ extension TrackerStore: TrackerStoreProtocol {
182186
fetchedResultsController.sections?[section].name
183187
}
184188

185-
func save(_ tracker: Tracker, in category: String) throws {
189+
func categoryStringForTracker(at indexPath: IndexPath) -> String? {
190+
let trackerCoreData = fetchedResultsController.object(at: indexPath)
191+
return trackerCoreData.initialCategory
192+
}
193+
194+
func save(_ tracker: Tracker, in category: String, isPinned: Bool) throws {
186195
let trackerCoreData = retrieveTrackerCoreDataEntity(for: tracker)
187-
let categoryCoreData = retrieveCategoryCoreDataEntity(for: category)
196+
let categoryCoreData = retrieveCategoryCoreDataEntity(for: isPinned ? L10n.Trackers.pinnedTitle : category)
188197
trackerCoreData.trackerID = tracker.id
189198
trackerCoreData.name = tracker.name
190199
trackerCoreData.colorHex = UIColorMarshalling.serialize(color: tracker.color)
191200
trackerCoreData.emoji = tracker.emoji
192201
trackerCoreData.scheduleString = tracker.schedule.map({ $0.rawValue }).joined(separator: ",")
202+
trackerCoreData.initialCategory = category
193203
trackerCoreData.category = categoryCoreData
194204
try context.save()
195205
}
@@ -221,4 +231,31 @@ extension TrackerStore: TrackerStoreProtocol {
221231
return Set<TrackerRecord>()
222232
}
223233
}
234+
235+
func pinTracker(at indexPath: IndexPath) throws {
236+
let pinnedTrackerCoreData = fetchedResultsController.object(at: indexPath)
237+
let pinnedCategoryCoreData: TrackerCategoryCoreData
238+
if let existingCategoryCoreData = retrieveCategoryCoreDataEntity(for: L10n.Trackers.pinnedTitle) {
239+
pinnedCategoryCoreData = existingCategoryCoreData
240+
} else {
241+
pinnedCategoryCoreData = TrackerCategoryCoreData(context: context)
242+
pinnedCategoryCoreData.title = L10n.Trackers.pinnedTitle
243+
pinnedCategoryCoreData.isCategorySelect = false
244+
}
245+
pinnedTrackerCoreData.category = pinnedCategoryCoreData
246+
try context.save()
247+
}
248+
249+
func unpinTracker(at indexPath: IndexPath) throws {
250+
let unpinnedTrackerCoreData = fetchedResultsController.object(at: indexPath)
251+
guard let initialCategoryString = unpinnedTrackerCoreData.initialCategory else { return }
252+
let initialCategoryCoreData = retrieveCategoryCoreDataEntity(for: initialCategoryString)
253+
unpinnedTrackerCoreData.category = initialCategoryCoreData
254+
try context.save()
255+
}
256+
257+
func checkTrackerIsPinned(at indexPath: IndexPath) -> Bool {
258+
let trackerCoreData = fetchedResultsController.object(at: indexPath)
259+
return trackerCoreData.category?.title == L10n.Trackers.pinnedTitle ? true : false
260+
}
224261
}

TrackerApp/Services/TrackerStore/TrackerStoreProtocol.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,13 @@ protocol TrackerStoreProtocol {
1111
var numberOfSections: Int { get }
1212
func numberOfItemsInSection(_ section: Int) -> Int
1313
func name(of section: Int) -> String?
14+
func categoryStringForTracker(at indexPath: IndexPath) -> String?
1415
func object(at indexPath: IndexPath) -> Tracker?
15-
func save(_ tracker: Tracker, in category: String) throws
16+
func save(_ tracker: Tracker, in category: String, isPinned: Bool) throws
1617
func deleteTracker(at indexPath: IndexPath) throws
1718
func trackersFor(_ currentDate: String, searchRequest: String?)
1819
func records(for trackerIndexPath: IndexPath) -> Set<TrackerRecord>
20+
func pinTracker(at indexPath: IndexPath) throws
21+
func unpinTracker(at indexPath: IndexPath) throws
22+
func checkTrackerIsPinned(at indexPath: IndexPath) -> Bool
1923
}

TrackerApp/Trackers/Categories-MVVM/Model/CategoryStore.swift

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ final class CategoryStore: NSObject {
1616
private lazy var fetchedResultsController: NSFetchedResultsController<TrackerCategoryCoreData> = {
1717
let fetchRequest = TrackerCategoryCoreData.fetchRequest()
1818
fetchRequest.sortDescriptors = [NSSortDescriptor(key: Constants.sortCategoriesKey, ascending: true)]
19+
fetchRequest.predicate = NSPredicate(format: "%K != %@",
20+
#keyPath(TrackerCategoryCoreData.title), L10n.Trackers.pinnedTitle)
1921
let controller = NSFetchedResultsController(fetchRequest: fetchRequest,
2022
managedObjectContext: context,
2123
sectionNameKeyPath: nil,

TrackerApp/Trackers/NewCategoryViewController.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ extension NewCategoryViewController: UITextFieldDelegate {
112112
}
113113

114114
func textFieldDidEndEditing(_ textField: UITextField) {
115-
guard textField.text != "" else {
115+
guard let text = textField.text, text != "", text != L10n.Trackers.pinnedTitle.dropFirst() else {
116116
disableDoneButton()
117117
return
118118
}

TrackerApp/Trackers/TrackerCreationViewController.swift

+5-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ final class TrackerCreationViewController: UIViewController {
1919
private var selectedEmoji: String?
2020
private var selectedColor: UIColor?
2121
private var trackerID: String?
22+
private var isPinned = false
2223
private var trackerNameTextFieldConstraint = NSLayoutConstraint()
2324

2425
private var isTrackerDataComplete: Bool {
@@ -190,13 +191,14 @@ final class TrackerCreationViewController: UIViewController {
190191
selectColorIfNeeded()
191192
}
192193

193-
func edit(existing tracker: Tracker, in category: String, with dayCounter: Int) {
194+
func edit(existing tracker: Tracker, in category: String, with dayCounter: Int, isPinned: Bool) {
194195
selectedCategory = category
195196
selectedTrackerName = tracker.name
196197
selectedSchedule = tracker.schedule
197198
selectedEmoji = tracker.emoji
198199
selectedColor = tracker.color
199200
trackerID = tracker.id
201+
self.isPinned = isPinned
200202
let daysTitle = String.localizedStringWithFormat(L10n.Trackers.trackedDaysTitle, dayCounter)
201203
daysCounterLabel.text = "\(dayCounter) " + daysTitle
202204
}
@@ -241,7 +243,7 @@ final class TrackerCreationViewController: UIViewController {
241243
color: selectedColor,
242244
emoji: selectedEmoji,
243245
schedule: selectedSchedule)
244-
try? trackerStore?.save(newTracker, in: selectedCategory)
246+
try? trackerStore?.save(newTracker, in: selectedCategory, isPinned: isPinned)
245247
closeControllerAction()
246248
}
247249

@@ -308,6 +310,7 @@ final class TrackerCreationViewController: UIViewController {
308310
mainTitleLabel.text = L10n.Trackers.editTrackerTitle
309311
trackerNameTextFieldConstraint.constant = 116
310312
setupDaysCounterLabelConstraints()
313+
createButton.setTitle(L10n.Trackers.saveTitle, for: .normal)
311314
}
312315
}
313316

TrackerApp/Trackers/TrackersViewController.swift

+44-15
Original file line numberDiff line numberDiff line change
@@ -201,12 +201,39 @@ final class TrackersViewController: UIViewController {
201201

202202
private func editTracker(at indexPath: IndexPath) {
203203
guard let tracker = trackerStore.object(at: indexPath),
204-
let category = trackerStore.name(of: indexPath.section) else { return }
204+
let category = trackerStore.categoryStringForTracker(at: indexPath) else { return }
205205
let dayCounter = trackerStore.records(for: indexPath).count
206-
let viewController = TrackerCreationViewController(trackerType: .existing)
207-
viewController.trackerStore = trackerStore
208-
viewController.edit(existing: tracker, in: category, with: dayCounter)
209-
present(viewController, animated: true)
206+
let editViewController = TrackerCreationViewController(trackerType: .existing)
207+
editViewController.trackerStore = trackerStore
208+
editViewController.edit(existing: tracker,
209+
in: category,
210+
with: dayCounter,
211+
isPinned: trackerStore.checkTrackerIsPinned(at: indexPath))
212+
present(editViewController, animated: true)
213+
}
214+
215+
private func deleteTracker(at indexPath: IndexPath) {
216+
let alert = UIAlertController(title: nil,
217+
message: L10n.Trackers.deleteConfirmationTitle,
218+
preferredStyle: .actionSheet)
219+
let deleteAction = UIAlertAction(title: L10n.Trackers.deleteTitle, style: .destructive) { [weak self] _ in
220+
try? self?.trackerStore.deleteTracker(at: indexPath)
221+
}
222+
let cancelAction = UIAlertAction(title: L10n.Trackers.cancelTitle, style: .cancel)
223+
[deleteAction, cancelAction].forEach { alert.addAction($0) }
224+
present(alert, animated: true)
225+
}
226+
227+
private func pinUnpinActionForTracker(at indexPath: IndexPath) {
228+
if trackerStore.checkTrackerIsPinned(at: indexPath) {
229+
try? trackerStore.unpinTracker(at: indexPath)
230+
} else {
231+
try? trackerStore.pinTracker(at: indexPath)
232+
}
233+
}
234+
235+
private func pinUnpinTitleForTracker(at indexPath: IndexPath) -> String {
236+
trackerStore.checkTrackerIsPinned(at: indexPath) ? L10n.Trackers.unpinTitle : L10n.Trackers.pinTitle
210237
}
211238
}
212239

@@ -264,16 +291,18 @@ extension TrackersViewController: UICollectionViewDelegate {
264291
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemsAt indexPaths: [IndexPath], point: CGPoint) -> UIContextMenuConfiguration? {
265292
guard indexPaths.count > 0 else { return nil }
266293
let indexPath = indexPaths[0]
267-
return UIContextMenuConfiguration(actionProvider: { actions in
268-
return UIMenu(children: [UIAction(title: L10n.Trackers.pinTitle) { _ in
269-
270-
},
271-
UIAction(title: L10n.Trackers.editTitle) { [weak self] _ in
272-
self?.editTracker(at: indexPath)
273-
},
274-
UIAction(title: L10n.Trackers.deleteTitle, attributes: .destructive) { _ in
275-
276-
}])
294+
return UIContextMenuConfiguration(actionProvider: { [weak self] actions in
295+
return UIMenu(children: [
296+
UIAction(title: self?.pinUnpinTitleForTracker(at: indexPath) ?? "") { _ in
297+
self?.pinUnpinActionForTracker(at: indexPath)
298+
},
299+
UIAction(title: L10n.Trackers.editTitle) { _ in
300+
self?.editTracker(at: indexPath)
301+
},
302+
UIAction(title: L10n.Trackers.deleteTitle, attributes: .destructive) { _ in
303+
self?.deleteTracker(at: indexPath)
304+
}
305+
])
277306
})
278307
}
279308

0 commit comments

Comments
 (0)