HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
장지원 페이지/
몰입 캠프
몰입 캠프
/
#20. Transfer Data!

#20. Transfer Data!

URL
날짜
Jul 16, 2024
Project
project3
주의사항!!!! 모바일 VPN도 꼭 켜야 한다!!!
 
주의사항!!!! 서버 업데이트 할 때 밑 처럼 업데이트 해야할 때도 있나봄..
코드…
val user = UserHolder.getUser() ?: User( userId = userId, userName = userName, heroName = heroName, age = age, level = 1, // Default values title = "삼류", coin = 0, ranking = 0, backgroundId = null, characterId = null ) // 사용자 정보 업데이트 user.userId = userId user.userName = userName user.heroName = heroName user.age = age user.title = "삼류"
→ 모르면 지피티한테 물어보자!!
 
  • 배경 추가하기
 
코드 keep
// DiaryActivity.kt package com.example.project3 import android.content.Intent import android.os.Bundle import android.os.Handler import android.os.Looper import android.util.Log import android.view.WindowManager import android.widget.Button import android.widget.ProgressBar import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.view.WindowCompat import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import retrofit2.Call import retrofit2.Callback import retrofit2.Response class DiaryActivity : AppCompatActivity() { private lateinit var questRecyclerView: RecyclerView private lateinit var questAdapter: QuestAdapter private lateinit var diaryRecyclerView: RecyclerView private lateinit var diaryAdapter: DiaryAdapter private lateinit var btnCreateDiary: Button override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_diary) WindowCompat.setDecorFitsSystemWindows(window, false) window.apply { addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) statusBarColor = android.graphics.Color.TRANSPARENT } // RecyclerView 설정 questRecyclerView = findViewById(R.id.diary_list1_RV) questRecyclerView.layoutManager = LinearLayoutManager(this) diaryRecyclerView = findViewById(R.id.diary_list2_RV) diaryRecyclerView.layoutManager = LinearLayoutManager(this) // 버튼 설정 및 클릭 이벤트 추가 btnCreateDiary = findViewById(R.id.diary_button2_BT) btnCreateDiary.setOnClickListener { // val intent = Intent(this, DiaryGenActivity::class.java) // startActivity(intent) updateAllUsersQuests() } // 다이어리 데이터 가져오기 fetchDiaries() fetchQuests() } private fun fetchDiaries() { val currentUser = UserHolder.getUser() if (currentUser == null) { Log.e("jangjiwon", "Current user is null") return } val currentUserId = currentUser.userId if (currentUserId == null) { Log.e("jangjiwon", "Current user ID is not a valid integer") return } ApiClient.apiService.getDiaries().enqueue(object : Callback<List<Diary>> { override fun onResponse(call: Call<List<Diary>>, response: Response<List<Diary>>) { if (response.isSuccessful && response.body() != null) { val allDiaries = response.body()!! Log.d("jangjiwon", "Fetched ${allDiaries.size} diaries") val userDiaries = allDiaries.filter { Log.d("jangjiwon", "Diary UserId: ${it.userId}, Current UserId: $currentUserId") it.userId == currentUserId } diaryAdapter = DiaryAdapter(userDiaries) { diary -> showDiaryDialog(diary) } diaryRecyclerView.adapter = diaryAdapter } else { Log.e("jangjiwon", "Response not successful: ${response.code()} - ${response.message()}") Log.e("jangjiwon", "Response body: ${response.errorBody()?.string()}") } } override fun onFailure(call: Call<List<Diary>>, t: Throwable) { Log.e("jangjiwon", "Failed to fetch diaries", t) } }) } private fun fetchQuests() { val currentUser = UserHolder.getUser() if (currentUser == null) { Log.e("jangjiwon", "Current user is null") return } ApiClient.apiService.getQuests().enqueue(object : Callback<List<Quest>> { override fun onResponse(call: Call<List<Quest>>, response: Response<List<Quest>>) { if (response.isSuccessful && response.body() != null) { val allQuests = response.body()!! Log.d("jangjiwon", "Fetched ${allQuests.size} quests") val userQuests = allQuests.filter { Log.d("jangjiwon", "Quest UserId: ${it.userId}, Current UserId: ${currentUser.userId}") it.userId == currentUser.userId } // 최신 세 개의 퀘스트만 가져오기 val topThreeQuests = userQuests.takeLast(3) questAdapter = QuestAdapter(topThreeQuests) { quest -> showQuestDialog(quest) } questRecyclerView.adapter = questAdapter } else { Log.e("jangjiwon", "Response not successful: ${response.code()} - ${response.message()}") } } override fun onFailure(call: Call<List<Quest>>, t: Throwable) { Log.e("jangjiwon", "Failed to fetch quests", t) } }) } private fun showDiaryDialog(diary: Diary) { val dialog = AlertDialog.Builder(this) .setTitle("Diary Details") .setPositiveButton("OK", null) .create() dialog.show() } private fun showQuestDialog(quest: Quest) { val dialogView = layoutInflater.inflate(R.layout.quest_dialog, null) val questTitle: TextView = dialogView.findViewById(R.id.quest_title) val questDetails: TextView = dialogView.findViewById(R.id.quest_details) val progressBar: ProgressBar = dialogView.findViewById(R.id.progress_bar) val timeRemaining: TextView = dialogView.findViewById(R.id.time_remaining) val startButton: Button = dialogView.findViewById(R.id.start_button) val stopButton: Button = dialogView.findViewById(R.id.stop_button) questTitle.text = "Quest ID: ${quest.questId}" questDetails.text = """ Title: ${quest.contents} Type: ${quest.type} Completion Status: ${if (quest.isComplete) "완료" else "미완료"} """.trimIndent() val completeTimeInSeconds = 1800 // 30 minutes val handler = Handler(Looper.getMainLooper()) var startTime = System.currentTimeMillis() var isRunning = false fun updateProgressTimeOnServer(progressTime: Int) { quest.progressTime = progressTime.toString() ApiClient.apiService.updateQuestProgress(quest.questId, quest).enqueue(object : Callback<Void> { override fun onResponse(call: Call<Void>, response: Response<Void>) { if (response.isSuccessful) { Log.d("DiaryActivity", "Progress time updated successfully") } else { Log.e("DiaryActivity", "Failed to update progress time: ${response.code()} - ${response.message()}") } } override fun onFailure(call: Call<Void>, t: Throwable) { Log.e("DiaryActivity", "Error updating progress time", t) } }) } val updateProgressBar = object : Runnable { override fun run() { if (!isRunning) return val elapsedTime = (System.currentTimeMillis() - startTime) / 1000 progressBar.progress = elapsedTime.toInt() val remainingTime = completeTimeInSeconds - elapsedTime val minutes = remainingTime / 60 val seconds = remainingTime % 60 timeRemaining.text = String.format("Time Remaining: %02d:%02d", minutes, seconds) if (elapsedTime < completeTimeInSeconds) { handler.postDelayed(this, 1000) } else { quest.isComplete = true questDetails.text = questDetails.text.toString() + "\nCompletion Time: ${quest.completeTime}" progressBar.progress = progressBar.max isRunning = false updateProgressTimeOnServer(progressBar.max) } updateProgressTimeOnServer(elapsedTime.toInt()) } } startButton.setOnClickListener { if (!isRunning) { startTime = System.currentTimeMillis() - (progressBar.progress * 1000) isRunning = true handler.post(updateProgressBar) } } stopButton.setOnClickListener { isRunning = false updateProgressTimeOnServer(progressBar.progress) } val dialog = AlertDialog.Builder(this) .setTitle("Quest Details") .setView(dialogView) .setPositiveButton("OK") { _, _ -> isRunning = false updateProgressTimeOnServer(progressBar.progress) } .create() // 다이얼로그가 열릴 때 서버에서 진행 시간 가져오기 ApiClient.apiService.getQuestProgress(quest.questId).enqueue(object : Callback<Quest> { override fun onResponse(call: Call<Quest>, response: Response<Quest>) { if (response.isSuccessful && response.body() != null) { val fetchedQuest = response.body()!! progressBar.progress = fetchedQuest.progressTime?.toIntOrNull() ?: 0 startTime = System.currentTimeMillis() - (progressBar.progress * 1000) if (progressBar.progress >= completeTimeInSeconds) { quest.isComplete = true questDetails.text = questDetails.text.toString() + "\nCompletion Time: ${quest.completeTime}" progressBar.progress = progressBar.max } } else { Log.e("DiaryActivity", "Failed to fetch progress time: ${response.code()} - ${response.message()}") } } override fun onFailure(call: Call<Quest>, t: Throwable) { Log.e("DiaryActivity", "Error fetching progress time", t) } }) dialog.show() } private fun updateAllUsersQuests() { ApiClient.apiService.updateAllUsersQuests().enqueue(object : Callback<Void> { override fun onResponse(call: Call<Void>, response: Response<Void>) { if (response.isSuccessful) { Log.d("DiaryActivity", "Successfully requested quest generation for all users") } else { Log.e("DiaryActivity", "Failed to request quest generation: ${response.code()} - ${response.message()}") } } override fun onFailure(call: Call<Void>, t: Throwable) { Log.e("DiaryActivity", "Request to generate quests failed", t) } }) } }
keep 2
// DiaryActivity.kt package com.example.project3 import android.content.Intent import android.os.Bundle import android.os.Handler import android.os.Looper import android.util.Log import android.view.WindowManager import android.widget.Button import android.widget.ImageView import android.widget.ProgressBar import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.view.WindowCompat import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import retrofit2.Call import retrofit2.Callback import retrofit2.Response class DiaryActivity : AppCompatActivity() { private lateinit var questRecyclerView: RecyclerView private lateinit var questAdapter: QuestAdapter private lateinit var diaryRecyclerView: RecyclerView private lateinit var diaryAdapter: DiaryAdapter private lateinit var btnCreateDiary: ImageView private lateinit var btnCreateToDo: ImageView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_diary) WindowCompat.setDecorFitsSystemWindows(window, false) window.apply { addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) statusBarColor = android.graphics.Color.TRANSPARENT } // RecyclerView 설정 questRecyclerView = findViewById(R.id.diary_list1_RV) questRecyclerView.layoutManager = LinearLayoutManager(this) diaryRecyclerView = findViewById(R.id.diary_list2_RV) diaryRecyclerView.layoutManager = LinearLayoutManager(this) // 버튼 설정 및 클릭 이벤트 추가 btnCreateDiary = findViewById(R.id.diary_button2_BT) btnCreateDiary.setOnClickListener { val intent = Intent(this, DiaryGenActivity::class.java) startActivity(intent) // updateAllUsersQuests() } btnCreateToDo = findViewById(R.id.diary_button1_BT) btnCreateToDo.setOnClickListener { // val intent = Intent(this, DiaryGenActivity::class.java) // startActivity(intent) updateAllUsersQuests() } // 다이어리 데이터 가져오기 fetchDiaries() fetchQuests() } private fun fetchDiaries() { val currentUser = UserHolder.getUser() if (currentUser == null) { Log.e("jangjiwon", "Current user is null") return } val currentUserId = currentUser.userId if (currentUserId == null) { Log.e("jangjiwon", "Current user ID is not a valid integer") return } ApiClient.apiService.getDiaries().enqueue(object : Callback<List<Diary>> { override fun onResponse(call: Call<List<Diary>>, response: Response<List<Diary>>) { if (response.isSuccessful && response.body() != null) { val allDiaries = response.body()!! Log.d("jangjiwon", "Fetched ${allDiaries.size} diaries") val userDiaries = allDiaries.filter { Log.d("jangjiwon", "Diary UserId: ${it.userId}, Current UserId: $currentUserId") it.userId == currentUserId } diaryAdapter = DiaryAdapter(userDiaries) { diary -> showDiaryDialog(diary) } diaryRecyclerView.adapter = diaryAdapter } else { Log.e("jangjiwon", "Response not successful: ${response.code()} - ${response.message()}") Log.e("jangjiwon", "Response body: ${response.errorBody()?.string()}") } } override fun onFailure(call: Call<List<Diary>>, t: Throwable) { Log.e("jangjiwon", "Failed to fetch diaries", t) } }) } private fun fetchQuests() { val currentUser = UserHolder.getUser() if (currentUser == null) { Log.e("jangjiwon", "Current user is null") return } ApiClient.apiService.getQuests().enqueue(object : Callback<List<Quest>> { override fun onResponse(call: Call<List<Quest>>, response: Response<List<Quest>>) { if (response.isSuccessful && response.body() != null) { val allQuests = response.body()!! Log.d("jangjiwon", "Fetched ${allQuests.size} quests") val userQuests = allQuests.filter { Log.d("jangjiwon", "Quest UserId: ${it.userId}, Current UserId: ${currentUser.userId}") it.userId == currentUser.userId } // 최신 세 개의 퀘스트만 가져오기 val topThreeQuests = userQuests.takeLast(3) questAdapter = QuestAdapter(topThreeQuests) { quest -> showQuestDialog(quest) } questRecyclerView.adapter = questAdapter } else { Log.e("jangjiwon", "Response not successful: ${response.code()} - ${response.message()}") } } override fun onFailure(call: Call<List<Quest>>, t: Throwable) { Log.e("jangjiwon", "Failed to fetch quests", t) } }) } private fun showDiaryDialog(diary: Diary) { val dialog = AlertDialog.Builder(this) .setTitle("Diary Details") .setPositiveButton("OK", null) .create() dialog.show() } private fun showQuestDialog(quest: Quest) { val dialogView = layoutInflater.inflate(R.layout.quest_dialog, null) val questTitle: TextView = dialogView.findViewById(R.id.quest_title) val questDetails: TextView = dialogView.findViewById(R.id.quest_details) val progressBar: ProgressBar = dialogView.findViewById(R.id.progress_bar) val timeRemaining: TextView = dialogView.findViewById(R.id.time_remaining) val startButton: Button = dialogView.findViewById(R.id.start_button) val stopButton: Button = dialogView.findViewById(R.id.stop_button) questTitle.text = "Quest ID: ${quest.questId}" questDetails.text = """ Title: ${quest.contents} Type: ${quest.type} Completion Status: ${if (quest.isComplete) "완료" else "미완료"} """.trimIndent() var startTime: Long = 0 var isRunning = false var elapsedTimeInSeconds: Int = 0 val completeTimeInSeconds = 1800 // 30 minutes fun updateProgressTimeOnServer(progressTime: Int) { quest.progressTime = progressTime.toString() ApiClient.apiService.updateQuestProgress(quest.questId, quest).enqueue(object : Callback<Void> { override fun onResponse(call: Call<Void>, response: Response<Void>) { if (response.isSuccessful) { Log.d("DiaryActivity", "Progress time updated successfully") } else { Log.e("DiaryActivity", "Failed to update progress time: ${response.code()} - ${response.message()}") } } override fun onFailure(call: Call<Void>, t: Throwable) { Log.e("DiaryActivity", "Error updating progress time", t) } }) } val handler = Handler(Looper.getMainLooper()) val updateRunnable = object : Runnable { override fun run() { if (isRunning) { val currentTime = System.currentTimeMillis() val timeDiff = (currentTime - startTime) / 1000 elapsedTimeInSeconds = timeDiff.toInt() progressBar.progress = elapsedTimeInSeconds val remainingTimeInSeconds = completeTimeInSeconds - elapsedTimeInSeconds val minutes = remainingTimeInSeconds / 60 val seconds = remainingTimeInSeconds % 60 timeRemaining.text = String.format("Time Remaining: %02d:%02d", minutes, seconds) if (elapsedTimeInSeconds >= completeTimeInSeconds) { quest.isComplete = true questDetails.text = questDetails.text.toString() + "\nCompletion Time: ${quest.completeTime}" progressBar.progress = progressBar.max isRunning = false updateProgressTimeOnServer(completeTimeInSeconds) } handler.postDelayed(this, 1000) } } } startButton.setOnClickListener { if (!isRunning) { isRunning = true startTime = System.currentTimeMillis() - (elapsedTimeInSeconds * 1000).toLong() handler.post(updateRunnable) } } stopButton.setOnClickListener { if (isRunning) { isRunning = false handler.removeCallbacks(updateRunnable) updateProgressTimeOnServer(elapsedTimeInSeconds) } } val dialog = AlertDialog.Builder(this) .setTitle("Quest Details") .setView(dialogView) .setPositiveButton("OK") { _, _ -> if (isRunning) { handler.removeCallbacks(updateRunnable) updateProgressTimeOnServer(elapsedTimeInSeconds) } } .create() // 다이얼로그가 열릴 때 서버에서 진행 시간 가져오기 ApiClient.apiService.getQuestProgress(quest.questId).enqueue(object : Callback<Quest> { override fun onResponse(call: Call<Quest>, response: Response<Quest>) { if (response.isSuccessful && response.body() != null) { val fetchedQuest = response.body()!! // Convert progress time from HH:mm:ss to total seconds val progressTime = calculateTotalSecondsFromString(fetchedQuest.progressTime ?: "00:00:00") progressBar.progress = progressTime elapsedTimeInSeconds = progressTime startTime = System.currentTimeMillis() - (elapsedTimeInSeconds * 1000).toLong() if (progressBar.progress >= completeTimeInSeconds) { quest.isComplete = true questDetails.text = questDetails.text.toString() + "\nCompletion Time: ${quest.completeTime}" progressBar.progress = progressBar.max } } else { Log.e("DiaryActivity", "Failed to fetch progress time: ${response.code()} - ${response.message()}") } } override fun onFailure(call: Call<Quest>, t: Throwable) { Log.e("DiaryActivity", "Error fetching progress time", t) } }) dialog.show() } // Function to calculate total seconds from HH:mm:ss formatted string private fun calculateTotalSecondsFromString(timeString: String): Int { val parts = timeString.split(":") if (parts.size != 3) return 0 val hours = parts[0].toIntOrNull() ?: 0 val minutes = parts[1].toIntOrNull() ?: 0 val seconds = parts[2].toIntOrNull() ?: 0 return hours * 3600 + minutes * 60 + seconds } private fun updateAllUsersQuests() { ApiClient.apiService.updateAllUsersQuests().enqueue(object : Callback<Void> { override fun onResponse(call: Call<Void>, response: Response<Void>) { if (response.isSuccessful) { Log.d("DiaryActivity", "Successfully requested quest generation for all users") } else { Log.e("DiaryActivity", "Failed to request quest generation: ${response.code()} - ${response.message()}") } } override fun onFailure(call: Call<Void>, t: Throwable) { Log.e("DiaryActivity", "Request to generate quests failed", t) } }) } }
 
의뢰소 + 기록실
 
얼추 서버 통신이랑 다이어로그 디자인만 하면 끝날 듯
 
다이어리 최근꺼 젤 위에 오게 + 로딩화면 구현 + 애니메이션 넣기
+ 다이어로그 다 디자인 다시 하기
 
코드 url
from django.urls import path, include from rest_framework.routers import DefaultRouter from .views import UserViewSet, DiaryViewSet, QuestViewSet, ItemViewSet, ReceiptViewSet, generate_quests_view from .views import GoogleLoginView router = DefaultRouter() router.register(r'users', UserViewSet) router.register(r'diaries', DiaryViewSet) router.register(r'quests', QuestViewSet) router.register(r'items', ItemViewSet) router.register(r'receipts', ReceiptViewSet) urlpatterns = [ path('', include(router.urls)), path('generate_quests/', generate_quests_view, name='generate_quests'), path('user_login/', GoogleLoginView.as_view(), name='user_login'), ]
코드 view
from rest_framework.decorators import api_view from rest_framework.response import Response from .models import User, Diary, Quest from .questGen import generate_quests import logging from rest_framework.views import APIView from rest_framework import viewsets, serializers, status from .models import User, Diary, Quest, Item, Receipt from .serializers import UserSerializer, DiarySerializer, QuestSerializer, ItemSerializer, ReceiptSerializer logger = logging.getLogger(__name__) class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() serializer_class = UserSerializer class DiaryViewSet(viewsets.ModelViewSet): queryset = Diary.objects.all() serializer_class = DiarySerializer class QuestViewSet(viewsets.ModelViewSet): queryset = Quest.objects.all() serializer_class = QuestSerializer class ItemViewSet(viewsets.ModelViewSet): queryset = Item.objects.all() serializer_class = ItemSerializer class ReceiptViewSet(viewsets.ModelViewSet): queryset = Receipt.objects.all() serializer_class = ReceiptSerializer @api_view(['POST']) def generate_quests_view(request): logger.debug("Starting quest generation for all users") users = User.objects.all() for user in users: # 다이어리 항목이 없으면 기본값으로 생성 diary = Diary.objects.filter(user=user).order_by('-created_at').first() if not diary: diary = Diary.objects.create( user=user, contents="오늘은 힘든 하루였다. 내일은 더 발전되는 삶을 살고 싶다." ) logger.debug(f"Created default diary for user {user.user_name}") # 퀘스트 생성 quests = generate_quests(user, diary) logger.debug(f"Generated {len(quests)} quests for user {user.user_name}") # 퀘스트 저장 for quest in quests: try: quest.save() logger.debug(f"Saved quest for user {user.user_name}: {quest.contents}") except Exception as e: logger.error(f"Failed to save quest for user {user.user_name}: {e}") return Response({"message": "Quests generated successfully for all users."}) ## 로그인 관련 로직(구글) class GoogleLoginSerializer(serializers.Serializer): google_user_id = serializers.CharField() user_name = serializers.CharField(required=False) hero_name = serializers.CharField(required=False) level = serializers.IntegerField(required=False) title = serializers.CharField(required=False) coin = serializers.IntegerField(required=False) age = serializers.IntegerField(required=False) ranking = serializers.IntegerField(required=False) background_id = serializers.IntegerField(required=False, allow_null=True) character_id = serializers.IntegerField(required=False, allow_null=True) class GoogleLoginView(APIView): def post(self, request, *args, **kwargs): serializer = GoogleLoginSerializer(data=request.data) if serializer.is_valid(): google_user_id = serializer.validated_data['google_user_id'] user_data = serializer.validated_data try: user = User.objects.get(user_id=google_user_id) # 기존 유저가 있으면 해당 유저 정보를 반환 user_info = UserSerializer(user).data return Response(user_info, status=status.HTTP_200_OK) except User.DoesNotExist: # 새로운 유저 생성 user = User.objects.create( user_id=google_user_id, user_name=user_data.get('user_name', 'Unknown'), hero_name=user_data.get('hero_name', 'New Hero'), level=user_data.get('level', 1), title=user_data.get('title', '삼류'), coin=user_data.get('coin', 0), age=user_data.get('age', 20), ranking=user_data.get('ranking', 1000), background_id=user_data.get('background_id'), character_id=user_data.get('character_id') ) user_info = UserSerializer(user).data return Response(user_info, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
 
.
from django.urls import path, include from rest_framework.routers import DefaultRouter from .views import UserViewSet, DiaryViewSet, QuestViewSet, ItemViewSet, ReceiptViewSet, generate_user_quests from .views import GoogleLoginView router = DefaultRouter() router.register(r'users', UserViewSet) router.register(r'diaries', DiaryViewSet) router.register(r'quests', QuestViewSet) router.register(r'items', ItemViewSet) router.register(r'receipts', ReceiptViewSet) urlpatterns = [ path('', include(router.urls)), path('generate_quests/<str:user_id>/', generate_user_quests, name='generate_quests'), path('user_login/', GoogleLoginView.as_view(), name='user_login'), ]
..
from rest_framework.decorators import api_view from rest_framework.response import Response from .models import User, Diary, Quest from .questGen import generate_quests import logging from rest_framework.views import APIView from rest_framework import viewsets, serializers, status from .models import User, Diary, Quest, Item, Receipt from .serializers import UserSerializer, DiarySerializer, QuestSerializer, ItemSerializer, ReceiptSerializer logger = logging.getLogger(__name__) class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() serializer_class = UserSerializer class DiaryViewSet(viewsets.ModelViewSet): queryset = Diary.objects.all() serializer_class = DiarySerializer class QuestViewSet(viewsets.ModelViewSet): queryset = Quest.objects.all() serializer_class = QuestSerializer class ItemViewSet(viewsets.ModelViewSet): queryset = Item.objects.all() serializer_class = ItemSerializer class ReceiptViewSet(viewsets.ModelViewSet): queryset = Receipt.objects.all() serializer_class = ReceiptSerializer @api_view(['POST']) def generate_user_quests(request, user_id): try: user = User.objects.get(id=user_id) except User.DoesNotExist: return Response({"message": f"User with id {user_id} does not exist."}, status=404) # 다이어리 항목이 없으면 기본값으로 생성 diary = Diary.objects.filter(user=user).order_by('-created_at').first() if not diary: diary = Diary.objects.create( user=user, contents="오늘은 힘든 하루였다. 내일은 더 발전되는 삶을 살고 싶다." ) logger.debug(f"Created default diary for user {user.user_name}") # 퀘스트 생성 quests = generate_quests(user, diary) logger.debug(f"Generated {len(quests)} quests for user {user.user_name}") # 퀘스트 저장 for quest in quests: try: quest.save() logger.debug(f"Saved quest for user {user.user_name}: {quest.contents}") except Exception as e: logger.error(f"Failed to save quest for user {user.user_name}: {e}") return Response({"message": f"Quests generated successfully for user {user.user_name}."}) ## 로그인 관련 로직(구글) class GoogleLoginSerializer(serializers.Serializer): google_user_id = serializers.CharField() user_name = serializers.CharField(required=False) hero_name = serializers.CharField(required=False) level = serializers.IntegerField(required=False) title = serializers.CharField(required=False) coin = serializers.IntegerField(required=False) age = serializers.IntegerField(required=False) ranking = serializers.IntegerField(required=False) background_id = serializers.IntegerField(required=False, allow_null=True) character_id = serializers.IntegerField(required=False, allow_null=True) class GoogleLoginView(APIView): def post(self, request, *args, **kwargs): serializer = GoogleLoginSerializer(data=request.data) if serializer.is_valid(): google_user_id = serializer.validated_data['google_user_id'] user_data = serializer.validated_data try: user = User.objects.get(user_id=google_user_id) # 기존 유저가 있으면 해당 유저 정보를 반환 user_info = UserSerializer(user).data return Response(user_info, status=status.HTTP_200_OK) except User.DoesNotExist: # 새로운 유저 생성 user = User.objects.create( user_id=google_user_id, user_name=user_data.get('user_name', 'Unknown'), hero_name=user_data.get('hero_name', 'New Hero'), level=user_data.get('level', 1), title=user_data.get('title', '삼류'), coin=user_data.get('coin', 0), age=user_data.get('age', 20), ranking=user_data.get('ranking', 1000), background_id=user_data.get('background_id'), character_id=user_data.get('character_id') ) user_info = UserSerializer(user).data return Response(user_info, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

컨셉
  1. 한 줄 일기
  1. 하루마다 지워지는 todo list
 

로딩화면은 어떻게 구현할까?

 
progress dialog라는 게 있다..
코드
package com.example.project3 import android.app.ProgressDialog import android.content.Intent import android.os.Bundle import android.os.Handler import android.os.Looper import android.util.Log import android.view.WindowManager import android.widget.Button import android.widget.ImageView import android.widget.ProgressBar import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.view.WindowCompat import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import retrofit2.Call import retrofit2.Callback import retrofit2.Response class DiaryActivity : AppCompatActivity() { private lateinit var questRecyclerView: RecyclerView private lateinit var questAdapter: QuestAdapter private lateinit var diaryRecyclerView: RecyclerView private lateinit var diaryAdapter: DiaryAdapter private lateinit var btnCreateDiary: ImageView private lateinit var btnCreateToDo: ImageView companion object { const val REQUEST_CODE_CREATE_DIARY = 1 } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_diary) WindowCompat.setDecorFitsSystemWindows(window, false) window.apply { addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) statusBarColor = android.graphics.Color.TRANSPARENT } // RecyclerView 설정 questRecyclerView = findViewById(R.id.diary_list1_RV) questRecyclerView.layoutManager = LinearLayoutManager(this) diaryRecyclerView = findViewById(R.id.diary_list2_RV) diaryRecyclerView.layoutManager = LinearLayoutManager(this) // 버튼 설정 및 클릭 이벤트 추가 btnCreateDiary = findViewById(R.id.diary_button2_BT) btnCreateDiary.setOnClickListener { val intent = Intent(this, DiaryGenActivity::class.java) startActivityForResult(intent, REQUEST_CODE_CREATE_DIARY) } btnCreateToDo = findViewById(R.id.diary_button1_BT) btnCreateToDo.setOnClickListener { updateCurrentUserQuests() } // 다이어리 데이터 가져오기 fetchDiaries() fetchQuests() } private fun fetchDiaries() { val currentUser = UserHolder.getUser() if (currentUser == null) { Log.e("jangjiwon", "Current user is null") return } val currentUserId = currentUser.userId if (currentUserId == null) { Log.e("jangjiwon", "Current user ID is not a valid integer") return } ApiClient.apiService.getDiaries().enqueue(object : Callback<List<Diary>> { override fun onResponse(call: Call<List<Diary>>, response: Response<List<Diary>>) { if (response.isSuccessful && response.body() != null) { val allDiaries = response.body()!! Log.d("jangjiwon", "Fetched ${allDiaries.size} diaries") val userDiaries = allDiaries.filter { Log.d("jangjiwon", "Diary UserId: ${it.userId}, Current UserId: $currentUserId") it.userId == currentUserId } diaryAdapter = DiaryAdapter(userDiaries) { diary -> showDiaryDialog(diary) } diaryRecyclerView.adapter = diaryAdapter } else { Log.e("jangjiwon", "Response not successful: ${response.code()} - ${response.message()}") Log.e("jangjiwon", "Response body: ${response.errorBody()?.string()}") } } override fun onFailure(call: Call<List<Diary>>, t: Throwable) { Log.e("jangjiwon", "Failed to fetch diaries", t) } }) } private fun fetchQuests() { val currentUser = UserHolder.getUser() if (currentUser == null) { Log.e("jangjiwon", "Current user is null") return } ApiClient.apiService.getQuests().enqueue(object : Callback<List<Quest>> { override fun onResponse(call: Call<List<Quest>>, response: Response<List<Quest>>) { if (response.isSuccessful && response.body() != null) { val allQuests = response.body()!! Log.d("jangjiwon", "Fetched ${allQuests.size} quests") val userQuests = allQuests.filter { Log.d("jangjiwon", "Quest UserId: ${it.userId}, Current UserId: ${currentUser.userId}") it.userId == currentUser.userId } // 최신 세 개의 퀘스트만 가져오기 val topThreeQuests = userQuests.takeLast(3) questAdapter = QuestAdapter(topThreeQuests) { quest -> showQuestDialog(quest) } questRecyclerView.adapter = questAdapter } else { Log.e("jangjiwon", "Response not successful: ${response.code()} - ${response.message()}") } } override fun onFailure(call: Call<List<Quest>>, t: Throwable) { Log.e("jangjiwon", "Failed to fetch quests", t) } }) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_CODE_CREATE_DIARY && resultCode == RESULT_OK) { // 다이어리 데이터 갱신 fetchDiaries() } } private fun showDiaryDialog(diary: Diary) { val dialog = AlertDialog.Builder(this) .setTitle("Diary Details") .setPositiveButton("OK", null) .create() dialog.show() } private fun showQuestDialog(quest: Quest) { val dialogView = layoutInflater.inflate(R.layout.quest_dialog, null) val questTitle: TextView = dialogView.findViewById(R.id.quest_title) val questDetails: TextView = dialogView.findViewById(R.id.quest_details) val progressBar: ProgressBar = dialogView.findViewById(R.id.progress_bar) val timeRemaining: TextView = dialogView.findViewById(R.id.time_remaining) val startButton: Button = dialogView.findViewById(R.id.start_button) val stopButton: Button = dialogView.findViewById(R.id.stop_button) questTitle.text = "Quest ID: ${quest.questId}" questDetails.text = """ Title: ${quest.contents} Type: ${quest.type} Completion Status: ${if (quest.isComplete) "완료" else "미완료"} """.trimIndent() var startTime: Long = 0 var isRunning = false var elapsedTimeInSeconds: Int = 0 val completeTimeInSeconds = 1800 // 30 minutes fun updateProgressTimeOnServer(progressTime: Int) { quest.progressTime = progressTime.toString() ApiClient.apiService.updateQuestProgress(quest.questId, quest).enqueue(object : Callback<Void> { override fun onResponse(call: Call<Void>, response: Response<Void>) { if (response.isSuccessful) { Log.d("DiaryActivity", "Progress time updated successfully") } else { Log.e("DiaryActivity", "Failed to update progress time: ${response.code()} - ${response.message()}") } } override fun onFailure(call: Call<Void>, t: Throwable) { Log.e("DiaryActivity", "Error updating progress time", t) } }) } fun updateUserExp() { val currentUser = UserHolder.getUser() if (currentUser != null) { currentUser.exp = (currentUser.exp ?: 0) + 10 Log.d("jangjiwon", "Updated experience: ${currentUser.exp}") ApiClient.apiService.updateUser(currentUser.userId!!, currentUser).enqueue(object : Callback<User> { override fun onResponse(call: Call<User>, response: Response<User>) { if (response.isSuccessful) { Log.d("jangjiwon", "User experience updated successfully") } else { Log.e("jangjiwon", "Failed to update user experience: ${response.code()} - ${response.message()}") } } override fun onFailure(call: Call<User>, t: Throwable) { Log.e("DiaryActivity", "Error updating user experience", t) } }) } } val handler = Handler(Looper.getMainLooper()) val updateRunnable = object : Runnable { override fun run() { if (isRunning) { val currentTime = System.currentTimeMillis() val timeDiff = (currentTime - startTime) / 1000 elapsedTimeInSeconds = timeDiff.toInt() progressBar.progress = elapsedTimeInSeconds val remainingTimeInSeconds = completeTimeInSeconds - elapsedTimeInSeconds val minutes = remainingTimeInSeconds / 60 val seconds = remainingTimeInSeconds % 60 timeRemaining.text = String.format("Time Remaining: %02d:%02d", minutes, seconds) if (elapsedTimeInSeconds >= completeTimeInSeconds) { quest.isComplete = true questDetails.text = questDetails.text.toString() + "\nCompletion Time: ${quest.completeTime}" progressBar.progress = progressBar.max isRunning = false updateProgressTimeOnServer(completeTimeInSeconds) updateUserExp() } handler.postDelayed(this, 1000) } } } startButton.setOnClickListener { if (!isRunning) { isRunning = true startTime = System.currentTimeMillis() - (elapsedTimeInSeconds * 1000).toLong() handler.post(updateRunnable) } } stopButton.setOnClickListener { if (isRunning) { isRunning = false handler.removeCallbacks(updateRunnable) updateProgressTimeOnServer(elapsedTimeInSeconds) } } val dialog = AlertDialog.Builder(this) .setTitle("Quest Details") .setView(dialogView) .setPositiveButton("OK") { _, _ -> if (isRunning) { handler.removeCallbacks(updateRunnable) updateProgressTimeOnServer(elapsedTimeInSeconds) } } .create() // 다이얼로그가 열릴 때 서버에서 진행 시간 가져오기 ApiClient.apiService.getQuestProgress(quest.questId).enqueue(object : Callback<Quest> { override fun onResponse(call: Call<Quest>, response: Response<Quest>) { if (response.isSuccessful && response.body() != null) { val fetchedQuest = response.body()!! // Convert progress time from HH:mm:ss to total seconds val progressTime = calculateTotalSecondsFromString(fetchedQuest.progressTime ?: "00:00:00") progressBar.progress = progressTime elapsedTimeInSeconds = progressTime startTime = System.currentTimeMillis() - (elapsedTimeInSeconds * 1000).toLong() if (progressBar.progress >= completeTimeInSeconds) { quest.isComplete = true questDetails.text = questDetails.text.toString() + "\nCompletion Time: ${quest.completeTime}" progressBar.progress = progressBar.max } } else { Log.e("DiaryActivity", "Failed to fetch progress time: ${response.code()} - ${response.message()}") } } override fun onFailure(call: Call<Quest>, t: Throwable) { Log.e("DiaryActivity", "Error fetching progress time", t) } }) dialog.show() } // Function to calculate total seconds from HH:mm:ss formatted string private fun calculateTotalSecondsFromString(timeString: String): Int { val parts = timeString.split(":") if (parts.size != 3) return 0 val hours = parts[0].toIntOrNull() ?: 0 val minutes = parts[1].toIntOrNull() ?: 0 val seconds = parts[2].toIntOrNull() ?: 0 return hours * 3600 + minutes * 60 + seconds } private fun updateCurrentUserQuests() { val progressDialog = ProgressDialog(this) progressDialog.setMessage("Updating quests...") progressDialog.setCancelable(false) progressDialog.show() val currentUser = UserHolder.getUser() if (currentUser == null) { progressDialog.dismiss() Log.e("jangjiwon", "Current user is null") return } val userId = currentUser.userId if (userId.isNullOrBlank()) { progressDialog.dismiss() Log.e("jangjiwon", "Invalid user ID") return } ApiClient.apiService.updateUserQuests(userId).enqueue(object : Callback<Void> { override fun onResponse(call: Call<Void>, response: Response<Void>) { progressDialog.dismiss() if (response.isSuccessful) { Log.d("DiaryActivity", "Successfully requested quest generation for user $userId") fetchDiaries() // Fetch updated diaries fetchQuests() // Fetch updated quests } else { Log.e("DiaryActivity", "Failed to request quest generation: ${response.code()} - ${response.message()}") } } override fun onFailure(call: Call<Void>, t: Throwable) { progressDialog.dismiss() Log.e("DiaryActivity", "Request to generate quests failed", t) } }) } }
코드
package com.example.project3 import android.content.Intent import android.os.Bundle import android.util.Log import android.view.WindowManager import android.widget.ImageView import android.widget.ProgressBar import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.view.WindowCompat import com.bumptech.glide.Glide import pl.droidsonroids.gif.GifImageView import retrofit2.Call import retrofit2.Callback import retrofit2.Response class MainActivity : AppCompatActivity() { private lateinit var userId: String private lateinit var gifImageView: GifImageView private lateinit var userNameTextView: TextView private lateinit var userHeroTextView: TextView private lateinit var userLvTextView: TextView private lateinit var userTitleTextView: TextView private lateinit var userCoinTextView: TextView private lateinit var progressBarExp: ProgressBar override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // image button setting val btnQuest = findViewById<ImageView>(R.id.button_quest) val btnStore = findViewById<ImageView>(R.id.button_store) val btnRanking = findViewById<ImageView>(R.id.button_ranking) // user id userId = UserHolder.getUser()?.userId.toString() // 상단 바를 투명하게 설정 WindowCompat.setDecorFitsSystemWindows(window, false) window.apply { addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) statusBarColor = android.graphics.Color.TRANSPARENT } btnQuest.setOnClickListener { val intent = Intent(this, DiaryActivity::class.java) startActivity(intent) } btnStore.setOnClickListener { val intent = Intent(this, StoreActivity::class.java) startActivity(intent) } btnRanking.setOnClickListener { val intent = Intent(this, RankingActivity::class.java) startActivity(intent) } // UI 요소 초기화 userNameTextView = findViewById(R.id.user_name) userHeroTextView = findViewById(R.id.user_hero) userLvTextView = findViewById(R.id.user_lv) userTitleTextView = findViewById(R.id.user_title) userCoinTextView = findViewById(R.id.text_coin) progressBarExp = findViewById(R.id.progressBar_exp) gifImageView = findViewById(R.id.gifImageView) // Load user data loadUserData() } override fun onResume() { super.onResume() // Update UI with UserHolder data UserHolder.getUser()?.let { updateUI(it) checkLevelUp(it) } } override fun onPause() { super.onPause() // Save current user level to SharedPreferences UserHolder.getUser()?.let { saveUserLevelToPrefs(it.exp?.div(100) ?: 0) } } private fun loadUserData() { val apiService = ApiClient.retrofit.create(APIServer::class.java) apiService.getUserById(userId).enqueue(object : Callback<User> { override fun onResponse(call: Call<User>, response: Response<User>) { if (response.isSuccessful) { val user = response.body() user?.let { UserHolder.setUser(it) updateUI(it) checkLevelUp(it) } } else { Log.e("MainActivity", "Response is not successful") } } override fun onFailure(call: Call<User>, t: Throwable) { Log.e("MainActivity", "Failed to get user data", t) } }) } private fun checkLevelUp(user: User) { val prefs = getSharedPreferences("user_prefs", MODE_PRIVATE) val savedLevel = prefs.getInt("user_level", 0) val currentLevel = user.exp?.div(100) ?: 0 if (currentLevel > savedLevel) { showLevelUpDialog(currentLevel) saveUserLevelToPrefs(currentLevel) } } private fun saveUserLevelToPrefs(level: Int) { val prefs = getSharedPreferences("user_prefs", MODE_PRIVATE) val editor = prefs.edit() editor.putInt("user_level", level) editor.apply() } private fun showLevelUpDialog(newLevel: Int) { val builder = AlertDialog.Builder(this) builder.setTitle("Level Up!") builder.setMessage("Congratulations! You have reached level $newLevel.") builder.setPositiveButton("OK") { dialog, _ -> dialog.dismiss() } val dialog = builder.create() dialog.show() } fun updateUI(user: User) { userNameTextView.text = user.userName userHeroTextView.text = user.heroName userLvTextView.text = "Lv: ${user.exp?.div(100)}" userTitleTextView.text = user.title userCoinTextView.text = user.coin.toString() progressBarExp.max = 100 progressBarExp.progress = user.exp?.rem(100) ?: 0 // character_id에 따라 이미지 설정 when (user.characterName) { "토끼" -> Glide.with(this).load(R.raw.rabbit).into(gifImageView) "버섯무리" -> Glide.with(this).load(R.raw.mushroom).into(gifImageView) "무사" -> Glide.with(this).load(R.raw.musa).into(gifImageView) "아이언맨" -> Glide.with(this).load(R.raw.ironman).into(gifImageView) else -> gifImageView.setImageResource(R.raw.default_char) } } }