티스토리 뷰
1. 데이터베이스와 API
1) 테이블

[calendar]
달력 정보가 담긴 테이블입니다.

[schedule]
일정 정보가 담긴 테이블입니다.

[plan]

[task]
계획 정보가 담긴 테이블입니다.
2) 일정 등록
@app.post("/insert/schedule")
async def insert_schedule(item: ScheduleItem):
schedule = create_schedule(item)
if schedule.recurrenceType != "none" and schedule.recurrenceEndDate is None:
raise HTTPException(status_code=400, detail="반복 종료 일 정보가 누락되었습니다.")
result = await insert_schedule_item(schedule)
if item.recurrenceType == "yearly":
await yearly_schedule(item, result)
elif item.recurrenceType == "monthly":
await monthly_schedule(item, result)
elif item.recurrenceType == "weekly":
await weekly_schedule(item, result)
elif item.recurrenceType == "daily":
await daily_schedule(item, result)
return "등록 완료"
일정의 경우 반복 설정이라는 기능이 필요합니다.
recurrenceType이 “none”이 아닐 때 recurrenceEndDate가 필수로 필요합니다.
만약 recurrenceEndDate의 값이 없다면 에러를 리턴합니다.
그다음 반복 일정의 경우 첫 일정의 id를 recurrenceId로 설정해야 합니다.
async def insert_schedule_item(schedule: Schedule) -> int:
session.add(schedule)
session.flush()
session.commit()
session.refresh(schedule)
return schedule.id
commit() 이후 refresh()를 이용하면 해당 로우의 값을 새로고침을 통해 autoincrement로 설정한 id의 값을 가져올 수 있습니다.
마지막으로 반복 타입에 맞게 반복 종료일까지 값을 증가시키면서 데이터베이스에 내용을 추가하였습니다.
from datetime import datetime, timedelta
async def weekly_schedule(item: ScheduleItem, id: int):
current = item.startTime + timedelta(days=7)
while current <= item.recurrenceEndDate:
item.startTime = current
item.endTime = item.endTime + timedelta(days=7)
await insert_schedule_item(create_schedule(item, id))
current = current + timedelta(days=7)
async def daily_schedule(item: ScheduleItem, id: int):
current = item.startTime + timedelta(days=1)
while current <= item.recurrenceEndDate:
item.startTime = current
item.endTime = item.endTime + timedelta(days=1)
await insert_schedule_item(create_schedule(item, id))
current = current + timedelta(days=1)
매주, 매일 반복의 경우 timedelta()를 이용하여 시간을 계산하였습니다.
async def yearly_schedule(item: ScheduleItem, id: int):
current = item.startTime.replace(year=item.startTime.year + 1)
while current <= item.recurrenceEndDate:
item.startTime = current
item.endTime = item.endTime.replace(year=item.endTime.year + 1)
await insert_schedule_item(create_schedule(item, id))
current = current.replace(year=current.year + 1)
async def monthly_schedule(item: ScheduleItem, id: int):
current = next_year_month(item.startTime)
while current <= item.recurrenceEndDate:
item.startTime = current
item.endTime = item.endTime.replace(year=current.year, month=current.month)
await insert_schedule_item(create_schedule(item, id))
current = next_year_month(current_date=current)
def next_year_month(current_date: datetime):
nextMonth = current_date.month + 1
nextYear = current_date.year
if nextMonth > 12:
nextMonth = 1
nextYear += 1
return current_date.replace(year=nextYear, month=nextMonth)
매년, 매월의 경우 replace()를 이용해서 계산을 하였습니다.
다른 방식을 이용해서 쉽게 할 수 있긴 하지만 외부 클라우드 서버를 이용하고 있어서
최대한 추가 설치가 필요 없는 방안으로 진행하였습니다.
3) 일정 조회
@app.get("/schedule")
async def read_schedule(date: datetime):
session.commit()
return session.query(Schedule).filter(
Schedule.startTime >= date,
Schedule.endTime <= date + timedelta(days=1)
).all()
특정 날짜의 일정을 조회합니다.
datetime로 정의되어 있기 때문에 크기 비교를 이용하였습니다.
4) 일정 삭제
@app.delete("/delete/schedule")
async def delete_schedule(id: int):
schedule = session.query(Schedule).filter(Schedule.id == id).first()
if not schedule:
raise HTTPException(status_code=404, detail="존재하지 않는 id입니다.")
session.query(Schedule).filter(Schedule.recurrenceId == id).delete(synchronize_session=False)
session.delete(schedule)
session.commit()
return "일정을 삭제하였습니다."
우선 기본적으로 id를 기준으로 일정을 삭제해 줍니다.
만약 반복 일정의 경우 원본 일정이 삭제되면 같이 삭제합니다.
데이터베이스 오류를 방지하기 위해 우선 반복 데이터부터 삭제 후 원본 데이터를 삭제합니다.
5) 계획 등록, 삭제, 조회
@app.post("/insert/plan")
async def insert_plan(title, date):
plan = Plan()
plan.title = title
plan.planDate = date
session.add(plan)
session.flush()
session.commit()
session.refresh(plan)
return plan.id
@app.post("/insert/task")
async def insert_task(item: TaskItem):
task = create_task(item)
session.add(task)
session.commit()
return f"{item.contents} 추가 완료"
@app.post("/insert/plan-tasks")
async def insert_plan_tasks(item: PlanTasks):
planId = await insert_plan(item.title, item.planDate)
for taskItem in item.taskList:
taskItem.planId = planId
await insert_task(taskItem)
return f"{item.title} 등록 완료"
@app.delete("/delete/plan-tasks")
async def delete_plan_tasks(id: int):
plan = session.query(Plan).filter(Plan.id == id).first()
if not plan:
raise HTTPException(status_code=404, detail="존재하지 않는 id입니다.")
session.query(Task).filter(Task.planId == id).delete(synchronize_session=False)
session.delete(plan)
session.commit()
return "계획 삭제 완료"
@app.get("/select/plans-tasks")
async def read_plans_tasks(date: str):
plan_list = session.query(Plan).filter(Plan.planDate == date).all()
return [
{
"id": plan.id,
"planDate": plan.planDate,
"title": plan.title,
"taskList": [
{
"id": task.id,
"contents": task.contents,
"isCompleted": task.isCompleted
}
for task in session.query(Task).filter(Task.planId == plan.id).all()
]
}
for plan in plan_list
]
일정과 동일한 방식으로 등록, 삭제, 조회를 진행하였습니다.
6) 달력 정보 조회
async def read_calendar(current_date: datetime):
format_date = current_date.strftime("%Y-%m-%d")
calendar_info = await read_calendar_info(format_date)
schedule_info = await read_schedule(current_date)
plan_info = await read_plans_tasks(format_date)
if calendar_info or schedule_info or plan_info:
return {
"date": format_date,
"calendarInfoList": [
{
"id": calendar.id,
"calendarDate": calendar.calendarDate,
"info": calendar.dateInfo,
"isHoliday": calendar.isHoliday,
"isSpecialDay": calendar.isSpecialDay
}
for calendar in calendar_info
],
"scheduleInfoList": [
{
"id": schedule.id,
"startTime": schedule.startTime,
"endTime": schedule.endTime,
"recurrenceType": schedule.recurrenceType,
"recurrenceEndDate": schedule.recurrenceEndDate,
"scheduleContent": schedule.scheduleContent,
"scheduleTitle": schedule.scheduleTitle,
"recurrenceId": schedule.recurrenceId
}
for schedule in schedule_info
],
"planInfoList": plan_info
}
원하는 날짜의 달력, 일정, 계획의 정보를 조회합니다.
이 부분이 이해가 안 되는 부분인데..
calendar_info = await read_calendar_info(format_date)
이렇게 데이터를 변수에 담은 다음
return {
"calendarInfoList": calendar_info
}
이런 식으로 리턴을 해도 문제가 없었습니다.
실제로 이런 방식으로 api를 호출하면 원하는 형식으로 데이터가 나옵니다.
@app.get("/select/calendar/month")
async def read_calendar_month(year: int, month: int):
session.commit()
start_date = datetime(year, month, 1)
end_date = get_last_day_time(year, month)
current_date = start_date
result = []
while current_date < end_date:
day_data = await read_calendar(current_date)
if day_data:
result.append(day_data)
current_date += timedelta(days=1)
return result
근데 여기서 월단위로 데이터를 만들기 위해 반복문으로 위의 함수를 호출하게 되면 데이터를 출력하지 않았습니다.
"calendarInfoList": [
{
"id": calendar.id,
"calendarDate": calendar.calendarDate,
"info": calendar.dateInfo,
"isHoliday": calendar.isHoliday,
"isSpecialDay": calendar.isSpecialDay
}
for calendar in calendar_info
],
그래서 이렇게 하니까 원하는 결과가 나오는데 무슨 차이인지 잘 모르겠습니다.
2. UI
1) 달력

달력 메인화면으로 오늘을 기준으로 해당 달력과 오늘의 일정/계획을 보여줍니다.
날짜를 변경하여 다른 날의 일정/계획을 보거나 추가할 수 있습니다.
일정/계획은 선택한 날짜에 추가한 아이템들을 리스트 형태로 표시합니다.
data class MyCalendar(
val date: String = "",
val isHoliday: Boolean = false,
val isSpecialDay: Boolean = false,
val dateInfo: String = "",
val dayOfWeek: String = "",
val detailDate: String = "",
val itemList: MutableList<CalendarItem> = mutableListOf(),
)
fun fetchMyCalendarByMonth(
year: Int,
month: Int
): List<MyCalendar> {
val calendar = Calendar.getInstance().apply {
set(Calendar.YEAR, year)
set(Calendar.MONTH, month - 1)
set(Calendar.DAY_OF_MONTH, 1)
}
val monthDays = calendar.getActualMaximum(Calendar.DAY_OF_MONTH)
val firstDay = calendar.get(Calendar.DAY_OF_WEEK)
val lastDayIndex = firstDay + monthDays - 2
val lastIndex = if (lastDayIndex < 35) 34 else 41
return (0..lastIndex)
.map {
if (it < firstDay - 1 || it > lastDayIndex) {
MyCalendar()
} else {
val date = (it - firstDay + 2).toString()
MyCalendar(
date = date,
dayOfWeek = getDayOfWeek(it),
detailDate = getDetailDate(year, month, date)
)
}
}
}
우선 달력을 표시하기 위해 달력 데이터를 만듭니다.
Calendar를 이용해서 1일부터 말일까지의 정보를 만듭니다.
편의를 위해 빈 공간도 MyCalendar()를 사용해서 리스트를 채웠습니다.
LazyVerticalGrid(
columns = GridCells.Fixed(7),
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalArrangement = Arrangement.spacedBy(2.dp),
modifier = Modifier.fillMaxWidth(),
) {
calendarInfo.forEach { info ->
if (info.date.isEmpty()) {
item { Box(modifier = Modifier) }
} else {
item {
DateCard(
calendarItem = info,
selectDate = selectDate,
today = today,
onClick = onSelectChange
)
}
}
}
}
LazyVerticalGrid를 이용하여 달력을 그려줍니다.
위에서 리스트를 만들 때 빈 공간의 경우 날짜 정보를 넣지 않았기 때문에 그것을 기준으로 UI를 표시합니다.
DateCard() 아이템에서 상태 값 같은 것은 관리를 하는데 어려운 코드는 아니므로 제외합니다.
2) 달력 (리스트 형태)

이전 페이지와 동일한 정보를 전체 리스트 형태로 보여줍니다.
일정, 계획 상관없이 동일한 날짜별로 묶어서 표시하며, 등록된 아이템이 없는 날짜는 표시하지 않습니다.
3) 일정 등록

일정의 날짜, 시작 시간, 종료 시간, 제목 내용을 입력받습니다.
반복 설정을 할 경우 반복 종료일 특수 창이 나타나며 필수로 입력해야 합니다.
4) 계획 등록

날짜와 제목, 내용을 입력받습니다.
- 버튼을 통해 내용의 개수를 늘릴 수 있으며 x 버튼을 통해 필요 없는 계획을 제거할 수 있습니다.
'안드로이드 > 코드' 카테고리의 다른 글
코드 분석하기 (0) | 2023.06.28 |
---|---|
Mj App : 게임 - 포켓몬 (0) | 2023.06.27 |
Mj App : 게임 - 엘소드 (0) | 2023.06.27 |
Mj App : 공통 (0) | 2023.06.27 |
Compose 내 관리앱 만들기 : 3. 교통 - 지하철 (0) | 2023.03.19 |
- Total
- Today
- Yesterday
- Compose BottomSheetScaffold
- Android Compose
- Compose MotionLayout
- WebView
- Compose ConstraintLayout
- Compose BottomSheetDialog
- Pokedex
- 안드로이드
- Kotlin
- Compose BottomSheet
- Compose 네이버 지도 api
- Compose 네이버 지도
- WorkManager
- Duplicate class fond 에러
- Retrofit
- Android
- Fast api
- Row
- column
- Duplicate class found error
- Compose QRCode Scanner
- 포켓몬 도감
- compose
- Compose ModalBottomSheetLayout
- Compose Naver Map
- LazyColumn
- Gradient
- 안드로이드 구글 지도
- Worker
- 웹뷰
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |