2026/03/02
오늘의 이야기
#billcorea #운동동아리관리앱
🏸 Schneedle,羽毛球俱乐部必备应用!
👉 比洞赛 – 记录分数并寻找对手 🎉
适合任何地方,独自一人、与朋友一起或在俱乐部! 🤝
如果你喜欢羽毛球,一定要尝试一下
前往应用程序👉 https://play.google.com/store/apps/details?id=com.billcorea.matchplay
오늘의 이야기
지난번에는 로그인하는 화면을 만들었으니, 이번에는 로그인해서 나오는 메인을 구현해 볼 요량이다.

메인화면이라고는 뭐 아직 버튼 2개와 타이틀 하나를 달았을 뿐이다.
이것이 그냥 Layout 을 이용해서 작업을 하고 있다면 조금은 더 쉽고 빠르게 될 것 같기는 하나, 이왕 배우기 시작한 jetpack compose을 활용해서 만들어 보기로 했다.
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import com.billcoreatech.multichat416.ui.theme.MultiChat416Theme
import com.billcoreatech.multichat416.widget.buttonAddChatRoom
import com.billcoreatech.multichat416.widget.buttonLogOut
import com.billcoreatech.multichat416.widget.titleView
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInClient
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.android.gms.tasks.OnCompleteListener
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.ktx.auth
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.database.ktx.database
import com.google.firebase.ktx.Firebase
class MainActivity : ComponentActivity() {
var TAG = "MainActivity"
lateinit var database : FirebaseDatabase
lateinit var chatroom : DatabaseReference
lateinit var googleSignInClient: GoogleSignInClient
lateinit var auth: FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val gso = GoogleSignInOptions
.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.default_web_client_id))
.requestEmail()
.build()
database = Firebase.database
chatroom = database.getReference("ChatRooms")
googleSignInClient = GoogleSignIn.getClient(this, gso)
auth = Firebase.auth
setContent {
MultiChat416Theme {
titleView(getString(R.string.titleChatRoom))
buttonAddChatRoom(applicationContext) {
}
buttonLogOut(applicationContext) {
doLogOut()
}
}
}
}
private fun doLogOut() {
googleSignInClient.signOut().addOnCompleteListener( OnCompleteListener {
task -> if (task.isComplete ) {
Log.e(TAG, "finish ... " )
auth.signOut()
finish()
}
})
}
}
MainActivity 에는 이제 꼭 필요한 코드만 들어가고 아무것도 없다. 화면에 구현된 타이틀 (titleView) 과 버튼 2개는 따로 빼서 class을 만들었다.
먼저 제목을 보여주는 titleView 의 코드
다른 activity 에서도 활용을 하기 위해서 parameter로 제목 이름을 받고 그것을 전체 화면 상단 오른쪽에 표시하도록 하였다.
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Composable
fun titleView(title:String) {
Column(modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
.padding(16.dp),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.Start,
){
Card(modifier = Modifier
.width(170.dp)
.height(60.dp)
.padding(5.dp),
shape = RoundedCornerShape(20.dp),
border = BorderStroke(width = 2.dp, color = Color.White),
) {
Text(text = title, color = Color.Blue
, modifier = Modifier.padding(10.dp))
}
}
}
다음은 화면 아래쪽에 있는 Floating Button 의 코드
버튼에 표시할 테스트를 string.xml 에서 가져오기 위해서 context을 전달했고, 버튼을 클릭했을 때 동작을 위에서 하기 위해 onClick 함수도 만들어 썼다.
import android.content.Context
import androidx.compose.foundation.layout.*
import androidx.compose.material.ExtendedFloatingActionButton
import androidx.compose.material.FloatingActionButtonDefaults
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.billcoreatech.multichat416.R
@Composable
fun buttonAddChatRoom(
context: Context,
onClick: () -> Unit
) {
var context = LocalContext.current
Column(modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
.padding(16.dp),
verticalArrangement = Arrangement.Bottom,
horizontalAlignment = Alignment.End
){
ExtendedFloatingActionButton(
backgroundColor = Color.White,
contentColor = Color.Blue,
icon = { Icon(Icons.Filled.Add,"") },
text = { Text(context.getString(R.string.addChatRoom)) },
onClick = { onClick() },
elevation = FloatingActionButtonDefaults.elevation(8.dp)
)
}
}
다음은 화면 상단에 있는 Logout 버튼
이 버튼 소스에서 context 을 전달해서 string.xml의 글자를 보여주도록 하였고, 버튼 click 처리를 하기 위해서 onClick을 전달했다. (사실은 받아와서 처리할 수 있도록 구현은 MainActivity에서...)
import android.content.Context
import androidx.compose.foundation.layout.*
import androidx.compose.material.ExtendedFloatingActionButton
import androidx.compose.material.FloatingActionButtonDefaults
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Logout
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.billcoreatech.multichat416.R
@Composable
fun buttonLogOut(
context: Context,
onClick: () -> Unit
) {
Column(modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
.padding(16.dp),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.End,
){
ExtendedFloatingActionButton(
backgroundColor = Color.White,
contentColor = Color.Blue,
icon = { Icon(Icons.Filled.Logout,"") },
text = { Text(context.getString(R.string.doLogout)) },
onClick = { onClick() },
elevation = FloatingActionButtonDefaults.elevation(8.dp)
)
}
}
이런 분리를 하니, 재활용성이나 Copy&Paste 해서 다른 함수로 옮겨 가는 것도 도움이 될 것 같다. 아무튼 오늘도 이 거 하나 만들어 내는데, 3시간이 훌쩍~ 이래서야 언제 다 끝낼 수 있을지???
오늘의 이야기
점심을 먹고 나선 길에서 만난 여름...

이렇게 보면 마치 어느 시골에 있는 가로수길 같은 느낌이 들기는 하다. 정작 이 곳은 길도 아니고 사무실 근처 정부청사앞 숲 공원의 나무들 사이다.
사진만 보면 여유로운 시간을 가지고 멀리 나들이 나간 것 같기는 하지만... 늘상의 시간이 지나가고 있고, 그 와중에 이 봄은 중간쯤 와 있는 것 같기는 하지만, 기온등의 날씨로 봐서는 선뜻 여름이 다가와 섰다고 느껴진다.
그렇게 또 한 계절이 스치고 지나간다. 나른한 봄 기운이 좋은 시절이기는 한데, 나의 마음에 아직도 어두운 밤 공기만 가득한 지 ? 긍정의 에너지만 있어야 할 것 같은 시기이기도 하지만, 정작은 ...
그래도 오늘 하루를 마무리 하며... 좋은 일만 함께 하길 바란다. 이 시간도 어느새 지나갈 테이니...
오늘의 이야기
#스하리1000명프로젝트,
韓国で迷子になりましたか?韓国語が話せなくても、このアプリを使えば簡単に移動できます。
あなたの言語で話すだけで、翻訳、検索が行われ、結果があなたの言語で表示されます。
旅行者に最適!英語、日本語、中国語、ベトナム語などを含む 10 以上の言語をサポートします。
今すぐ試してみましょう!
https://play.google.com/store/apps/details?id=com.billcoreatech.opdgang1127
2026/03/01
오늘의 이야기
이 나라에는 여러 나라의 사람 들어와 살고 있다. 코리안 드림을 꿈꾸며... 한 때는 이 나라의 사람들이 아메리칸드림을 꿈꾸며, 미국으로 떠났던 것처럼... 그래서 이번에는 다국적 언어를 이용한 채팅앱을 하나 만들어 볼까 한다.
잘 될지는 모르겠지만, 그러면서 Jetpack Compose 을 이용한 화면 구성 등에 대한 공부를 해 볼 요량이다.
오늘은 그 처음으로 로그인 페이지를 하나 만들어 보고자 한다.

이 화면을 layout 으로 그리라고 했다면
아마도 금새 그리지 않았을까 하는 생각이 든다.
화면에 Card box 와 Text가 들어간 button 기능 3개
제목 이름 표시 하나
그런데, 난 이 화면 하나를 그리기 위해서 며칠을 고민했고, 예제를 하나 찾았다. 그리고 그 예제를 참고해 가면서 화면을 그렸다.
그러자니, 3시간쯤... 지났다.
예전 project 파일에서 필요한 기능 몇개를 가지고 왔고, 그리고는 이제 완성이 되었다.
그냥 layout 으로 그리는 것이 나은가? 쉽게 그리는 데는 그것이 낫을 것 같기도 하다.
화면을 그린 source 을 살펴보자.
import android.app.Activity
import android.content.Intent
import android.content.IntentSender
import android.content.SharedPreferences
import android.os.Bundle
import android.preference.PreferenceManager
import android.preference.PreferenceManager.getDefaultSharedPreferences
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.billcoreatech.multichat416.ui.theme.MultiChat416Theme
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInClient
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.tasks.OnCompleteListener
import com.google.android.play.core.appupdate.AppUpdateInfo
import com.google.android.play.core.appupdate.AppUpdateManager
import com.google.android.play.core.appupdate.AppUpdateManagerFactory
import com.google.android.play.core.install.model.AppUpdateType
import com.google.android.play.core.install.model.UpdateAvailability
import com.google.android.play.core.review.ReviewInfo
import com.google.android.play.core.review.ReviewManager
import com.google.android.play.core.review.ReviewManagerFactory
import com.google.android.play.core.review.model.ReviewErrorCode
import com.google.android.play.core.tasks.RuntimeExecutionException
import com.google.android.play.core.tasks.Task
import com.google.firebase.appcheck.FirebaseAppCheck
import com.google.firebase.appcheck.debug.DebugAppCheckProviderFactory
import com.google.firebase.appcheck.safetynet.SafetyNetAppCheckProviderFactory
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.GoogleAuthProvider
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
import java.text.SimpleDateFormat
import java.util.*
class GoogleLogin : ComponentActivity() {
var appUpdateManager: AppUpdateManager? = null
var appUpdateInfoTask: Task<AppUpdateInfo>? = null
lateinit var sp: SharedPreferences
lateinit var editor: SharedPreferences.Editor
lateinit var sdf: SimpleDateFormat
lateinit var today: Calendar
lateinit var googleSignInClient: GoogleSignInClient
lateinit var auth: FirebaseAuth
val MY_REQUEST_CODE = 1000
var RC_SIGN_IN:Int = 1001
var TAG = "GoogleLogin"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val gso = GoogleSignInOptions
.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.default_web_client_id))
.requestEmail()
.build()
googleSignInClient = GoogleSignIn.getClient(this, gso)
auth = Firebase.auth
setContent {
MultiChat416Theme {
fun signIn() {
val signInIntent = googleSignInClient.signInIntent
startActivityForResult(signInIntent, RC_SIGN_IN)
}
fun doLogOut() {
googleSignInClient.signOut().addOnCompleteListener( OnCompleteListener {
task -> if (task.isComplete ) {
Log.e(TAG, "finish ... " )
auth.signOut()
finish()
}
})
}
Column(modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
){
Text(text = buildAnnotatedString {
withStyle(style = SpanStyle(color = Color.Red)) {
append("M")
}
withStyle(style = SpanStyle(color = Color.Black)) {
append("ulti")
}
withStyle(style = SpanStyle(color = Color.Red)) {
append(" L")
}
withStyle(style = SpanStyle(color = Color.Black)) {
append("anguage")
}
withStyle(style = SpanStyle(color = Color.Red)) {
append(" C")
}
withStyle(style = SpanStyle(color = Color.Black)) {
append("hatting")
}
}, fontSize = 30.sp)
Spacer(Modifier.size(32.dp))
Card(modifier = Modifier
.width(200.dp)
.height(100.dp)
.padding(8.dp),
) {
TextButton(onClick = {
signIn()
},content={
Text(text = getString(R.string.title_activity_google_login), color = Color.Blue)
})
}
Spacer(Modifier.size(8.dp))
Card(modifier = Modifier
.width(200.dp)
.height(100.dp)
.padding(8.dp),
) {
TextButton(onClick = {
Toast.makeText(applicationContext,
getString(R.string.mesgRegistorOnlyGoogle),
Toast.LENGTH_SHORT
).show()
}) {
Text(text = getString(R.string.doRegister), color = Color.Red)
}
}
Spacer(Modifier.size(8.dp))
Card(modifier = Modifier
.width(200.dp)
.height(100.dp)
.padding(8.dp),
) {
TextButton(onClick = {
doLogOut()
}) {
Text(text = getString(R.string.doLogout), color = Color.Gray)
}
}
}
}
}
val firebaseAppCheck = FirebaseAppCheck.getInstance()
firebaseAppCheck.installAppCheckProviderFactory(
if (BuildConfig.DEBUG_MODE) {
Log.e(TAG, "DebugAppCheckProviderFactory ...")
DebugAppCheckProviderFactory.getInstance() // AVD 에서 적용시
//SafetyNetAppCheckProviderFactory.getInstance() // 폰에서 적용시
} else {
Log.e(TAG, "SafetyNetAppCheckProviderFactory ...")
SafetyNetAppCheckProviderFactory.getInstance() // 폰에서 적용시
}
)
appUpdateManager = AppUpdateManagerFactory.create(this@GoogleLogin)
// Returns an intent object that you use to check for an update.
appUpdateInfoTask = appUpdateManager!!.appUpdateInfo
doUpdateCheck()
sp = getSharedPreferences("MultiChat", MODE_PRIVATE)
editor = sp.edit()
sdf = SimpleDateFormat("yyyyMMdd")
today = Calendar.getInstance()
if (!sp.getBoolean("REVIEW", false)) {
editor.putString("REVIEW_DATE", sdf.format(today.timeInMillis))
editor.commit()
}
// 사용하고 10일 지나가면 ...
if ((today.timeInMillis - sdf.parse(sp.getString("REVIEW_DATE",sdf.format(today.timeInMillis))).time) / (1000 * 60 * 60 * 24) > 10) {
var manager = ReviewManagerFactory.create(this@GoogleLogin)
var request = manager.requestReviewFlow()
editor.putBoolean("REVIEW", true)
editor.commit()
request.addOnCompleteListener { task ->
if (task.isSuccessful) {
// We got the ReviewInfo object
val reviewInfo = task.result
if (sp.getBoolean("REVIEW", false)) {
doReviseInfo(manager, this@GoogleLogin, reviewInfo)
}
} else {
// There was some problem, log or handle the error code.
@ReviewErrorCode val reviewErrorCode = (task.getException() as RuntimeExecutionException).errorCode
}
}
}
}
private fun doUpdateCheck() {
// Checks that the platform will allow the specified type of update.
appUpdateInfoTask!!.addOnSuccessListener { appUpdateInfo: AppUpdateInfo ->
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
// This example applies an immediate update. To apply a flexible update
// instead, pass in AppUpdateType.FLEXIBLE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)
) {
// Request the update.
try {
doRequestUpdate(appUpdateInfo)
} catch (e: IntentSender.SendIntentException) {
e.printStackTrace()
}
}
}
}
@Throws(IntentSender.SendIntentException::class)
private fun doRequestUpdate(appUpdateInfo: AppUpdateInfo) {
Log.e(TAG, "do Update Start")
appUpdateManager!!.startUpdateFlowForResult( // Pass the intent that is returned by 'getAppUpdateInfo()'.
appUpdateInfo, // Or 'AppUpdateType.FLEXIBLE' for flexible updates.
AppUpdateType.IMMEDIATE, // The current activity making the update request.
this, // Include a request code to later monitor this update request.
MY_REQUEST_CODE
)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == MY_REQUEST_CODE) {
if (resultCode != RESULT_OK) {
Log.e(TAG, "Update flow failed! Result code: $resultCode")
// If the update is cancelled or fails,
// you can request to start the update again.
}
}
if (requestCode == RC_SIGN_IN) {
val task = GoogleSignIn.getSignedInAccountFromIntent(data)
try {
// Google Sign In was successful, authenticate with Firebase
val account = task.getResult(ApiException::class.java)!!
Log.e(TAG, "firebaseAuthWithGoogle: ${account.id}")
firebaseAuthWithGoogle(account.idToken!!)
} catch (e: ApiException) {
// Google Sign In failed, update UI appropriately
Log.e(TAG, "Google sign in failed ${e.message}")
}
}
}
private fun firebaseAuthWithGoogle(idToken: String) {
val credential = GoogleAuthProvider.getCredential(idToken, null)
auth.signInWithCredential(credential)
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
// Sign in success, update UI with the signed-in user's information
Log.e(TAG, "signInWithCredential:success")
val user = auth.currentUser
var intent = Intent(this@GoogleLogin, MainActivity::class.java)
startActivity(intent)
finish()
} else {
// If sign in fails, display a message to the user.
Log.w(TAG, "signInWithCredential:failure", task.exception)
}
}
}
private fun doReviseInfo(manager: ReviewManager, activity: Activity, reviewInfo: ReviewInfo) {
val flow = manager.launchReviewFlow(activity, reviewInfo)
flow.addOnCompleteListener { _ ->
editor.putBoolean("REVIEW", false)
editor.putString("REVIEW_DATE", sdf.format(today.timeInMillis))
editor.commit()
}
}
}다른 건 볼 꺼 없고, setContent 함수에 들어 있는 것만 보면 될 것 같다. 전체 흐름을 알아야 하기 때문에,
setContent {
// 테마이름은 개별적이므로 참고만...
MultiChat416Theme {
.....
// 먼저 아래로 배열을 하기 위해서 선언 linearlayout 처럼 사용...
Column(modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
){
// 제목을 표시 한다. 첫글짜만, 대문자로 표시, 폰트색상도 빨간색...
Text(text = buildAnnotatedString {
withStyle(style = SpanStyle(color = Color.Red)) {
append("M")
}
withStyle(style = SpanStyle(color = Color.Black)) {
append("ulti")
}
withStyle(style = SpanStyle(color = Color.Red)) {
append(" L")
}
withStyle(style = SpanStyle(color = Color.Black)) {
append("anguage")
}
withStyle(style = SpanStyle(color = Color.Red)) {
append(" C")
}
withStyle(style = SpanStyle(color = Color.Black)) {
append("hatting")
}
}, fontSize = 30.sp)
// 여백 두기
Spacer(Modifier.size(32.dp))
// button 을 구분하기 위해서 네모상자에 넣는다.
Card(modifier = Modifier
.width(200.dp)
.height(100.dp)
.padding(8.dp),
) {
TextButton(onClick = {
.....
},content={
Text(text = getString(R.string.title_activity_google_login), color = Color.Blue)
})
}
// 여백 두기
Spacer(Modifier.size(8.dp))
// button 을 구분하기 위해서 네모상자에 넣는다.
Card(modifier = Modifier
.width(200.dp)
.height(100.dp)
.padding(8.dp),
) {
TextButton(onClick = {
Toast.makeText(applicationContext,
getString(R.string.mesgRegistorOnlyGoogle),
Toast.LENGTH_SHORT
).show()
}) {
Text(text = getString(R.string.doRegister), color = Color.Red)
}
}
// 여백 두기
Spacer(Modifier.size(8.dp))
// button 을 구분하기 위해서 네모상자에 넣는다.
Card(modifier = Modifier
.width(200.dp)
.height(100.dp)
.padding(8.dp),
) {
TextButton(onClick = {
.....
}) {
Text(text = getString(R.string.doLogout), color = Color.Gray)
}
}
}
}
}대략적으로 이런 정도의 코드로 위에 나오는 화면을 구성을 하게 된다. 이걸 Layout으로 만들어 보면 얼마나 걸릴까?
아무튼 오늘은 1일 차 coding 끝.
다음은 채팅목록을 구현해 보겠다. Database 은 Firebase 의 Realtime database 을 활용할 생각이다.
오늘의 이야기
https://rrtutors.com/tutorials/implement-room-database-in-jetpack-compose
Flutter Mobile app,Flutter widgets,Jetpack Compose Tutorial,Create Android App,Java,python,Ruby
Create Flutter applications,Jetpack Compose Tutorial,Create android application,Python,Java,RxJava,dart,GoLang examples
rrtutors.com
사실은 room database 가 어떤 것으로 구성해야 하는 가에 대한 고민을 하는 과정에서 찾아보게 된 site인데,
내용을 보던 중에 여러가지 예제를 볼 수 있었다. 샘플 코드를 보고 이것저것 해 보면 compose을 어떻게 구현해야 하는 가에 대한 이해를 할 수 있을 것 같아서 기록해 두기로 한다.
어젯밤 이후에 확인된 부분만 간단하게 기록을 해 보면 다음과 같다.






이것들을 하나씩 찾아서 수정된 부분으로 실행을 해 보아야 하겠지만, 그런 정도의 수고(?)는 해야 배우는 맛이 나지 않겠는가?
코드 작업을 할 때 참고할 부분이 많을 것 같은 예제를 찾았다. 잘 뜯어 보면서 나의 코드를 만들어 보아야 겠다.
다음엔 나의 코드 이야기를 적어 볼 수 있기를 바라며...
오늘의 이야기
#스치니1000프로젝트 #재미 #행운기원 #Compose #Firebase
🎯 야 너 토요일마다 로또 확인하냐?
나도 맨날 "혹시나~" 하면서 봤거든 ㅋㅋ
근데 이제는 그냥 안 해
AI한테 맡겼어 🤖✨
그것도 구글 Gemini로다가!
그래서 앱 하나 만들었지
👉 "로또 예상번호 by Gemini" 🎱
AI가 분석해서 번호 딱! 뽑아줌
그냥 보고 참고만 하면 됨
재미로 해도 좋고…
혹시 모르는 거잖아? 😏
https://play.google.com/store/apps/details?id=com.billcorea.gptlotto1127
오늘의 이야기
#스하리1000명프로젝트
스치니들!
내가 만든 이 앱은, 내 폰에 오는 알림 중에서 중요한 키워드가 있는 경우
등록해둔 친구에게 자동으로 전달해주는 앱이야 📲
예를 들어, 카드 결제 알림을 와이프나 자녀에게 보내주거나
이번 달 지출을 달력처럼 확인할 수도 있어!
앱을 함께 쓰려면 친구도 설치 & 로그인해줘야 해.
그래야 친구 목록에서 서로 선택할 수 있으니까~
서로 써보고 불편한 점 있으면 알려줘 🙏
👉 https://play.google.com/store/apps/details?id=com.nari.notify2kakao
오늘의 이야기
어제는 오랜만에 갑천변을 걸었다. 길이 끝이 나지 않도록... 갑천을 걷고 있었는 데, 강가에 한쌍의 새가 있어서 사진을 찍어 보기로 했다.
내가 지금 사용하는 폰은 갤노트 20인데, 카메라가 30배까지의 줌을 지원한다. 그래서 그냥 30배로 확대해서 찍어 보았다.


일반적인 사진을 촬영할 때는 상관이 없었는데, 30배 줌으로 했더니, 흔들리기도 하고 사진이 명확하기 나오지 않는다. 이럴 때는 삼각대를 설치해야 하는 건가?
삼각대가 있었더면 이 보다는 조금 더 선명하게 촬영을 할 수 있었을까?
오늘도 날은 좋다. 창가 베란다에 않아서 이런 글을 적고 있는 지금은 밖의 기온은 따듯하다. 이 안이 덥게 느껴지는 것으로 봐서는...
오늘의 이야기
https://medium.com/@alexstyl/views-to-composables-d715b92c6055
From View to Composable: A Quick jump to Jetpack Compose from a Android View mindset (with Cheat…
Learn how to apply your View knowledge to speed up your Compose learning.
medium.com
오늘도 Jetpack Compose을 활용한 화면 구현에 대한 내용을 구글링을 통해서 보고 있는 데, 이런 정도의 자료가 있으면 도움이 될 듯하여 링크를 달아 둔다.
아직은 시작인 단계 이기 때문에 많은 정보를 찾아서 적어 두는 것이 나중에 배움을 구할 때 도움이 될 거 라고 생각된다.

하나씩 만들어 보자... 오늘도 파이팅!

