Skip to content

Commit 78b3428

Browse files
authentication added
1 parent bb106ad commit 78b3428

23 files changed

Lines changed: 992 additions & 80 deletions

app/build.gradle

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ android {
1010
namespace = "com.kharagedition.tibetankeyboard"
1111
defaultConfig {
1212
applicationId "com.kharagedition.tibetankeyboard"
13-
minSdkVersion 21
13+
minSdkVersion 23
1414
targetSdkVersion 35
1515
versionCode 12
1616
versionName "1.3.13"
@@ -63,6 +63,10 @@ dependencies {
6363
implementation 'com.google.android.gms:play-services-ads:23.6.0'
6464
implementation platform('com.google.firebase:firebase-bom:33.9.0')
6565
implementation 'com.google.firebase:firebase-crashlytics-ktx'
66+
implementation 'com.google.firebase:firebase-auth-ktx'
67+
implementation 'com.google.android.gms:play-services-auth:20.7.0'
68+
implementation 'com.airbnb.android:lottie:6.2.0'
69+
implementation 'com.github.bumptech.glide:glide:4.16.0'
6670
implementation 'com.google.firebase:firebase-analytics:22.2.0'
6771
implementation 'com.google.firebase:firebase-messaging-ktx:24.1.0'
6872
implementation 'com.github.bumptech.glide:glide:4.15.1'

app/src/main/AndroidManifest.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@
1616
android:roundIcon="@drawable/icon_launcher"
1717
android:supportsRtl="true"
1818
android:theme="@style/Theme.TibetanKeyboard">
19+
<activity
20+
android:name=".LoginActivity"
21+
android:exported="false" />
1922
<activity
2023
android:name=".ui.ChatActivity"
2124
android:exported="false"
22-
android:windowSoftInputMode="adjustResize"/>
25+
android:windowSoftInputMode="adjustResize" />
2326
<activity
2427
android:name=".SettingsActivity"
2528
android:exported="true"
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
package com.kharagedition.tibetankeyboard
2+
3+
import android.content.Intent
4+
import android.graphics.Color
5+
import android.net.Uri
6+
import android.os.Bundle
7+
import android.text.SpannableString
8+
import android.text.Spanned
9+
import android.text.TextPaint
10+
import android.text.method.LinkMovementMethod
11+
import android.text.style.ClickableSpan
12+
import android.view.View
13+
import android.widget.TextView
14+
import android.widget.Toast
15+
import androidx.activity.result.contract.ActivityResultContracts
16+
import androidx.appcompat.app.AppCompatActivity
17+
import androidx.core.content.ContextCompat
18+
import androidx.core.view.ViewCompat
19+
import androidx.core.view.WindowInsetsCompat
20+
import com.google.android.gms.auth.api.signin.GoogleSignIn
21+
import com.google.android.gms.auth.api.signin.GoogleSignInClient
22+
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
23+
import com.google.android.gms.common.api.ApiException
24+
import com.google.android.material.button.MaterialButton
25+
import com.google.android.material.progressindicator.CircularProgressIndicator
26+
import com.google.firebase.auth.FirebaseAuth
27+
import com.google.firebase.auth.GoogleAuthProvider
28+
import com.google.firebase.auth.ktx.auth
29+
import com.google.firebase.ktx.Firebase
30+
import com.kharagedition.tibetankeyboard.R
31+
import com.kharagedition.tibetankeyboard.UserPreferences
32+
import com.kharagedition.tibetankeyboard.ui.ChatActivity
33+
34+
class LoginActivity : AppCompatActivity() {
35+
36+
private lateinit var auth: FirebaseAuth
37+
private lateinit var googleSignInClient: GoogleSignInClient
38+
private lateinit var buttonGoogleSignIn: MaterialButton
39+
private lateinit var progressIndicator: CircularProgressIndicator
40+
private lateinit var userPreferences: UserPreferences
41+
private lateinit var textViewPrivacyPolicy: TextView
42+
43+
private val googleSignInLauncher = registerForActivityResult(
44+
ActivityResultContracts.StartActivityForResult()
45+
) { result ->
46+
val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
47+
try {
48+
val account = task.getResult(ApiException::class.java)!!
49+
firebaseAuthWithGoogle(account.idToken!!)
50+
} catch (e: ApiException) {
51+
hideLoading()
52+
Toast.makeText(this, "Google sign in failed: ${e.message}", Toast.LENGTH_SHORT).show()
53+
}
54+
}
55+
56+
override fun onCreate(savedInstanceState: Bundle?) {
57+
super.onCreate(savedInstanceState)
58+
setContentView(R.layout.activity_login)
59+
60+
// Initialize Firebase Auth
61+
auth = Firebase.auth
62+
userPreferences = UserPreferences(this)
63+
64+
// Check if user is already signed in
65+
if (auth.currentUser != null && userPreferences.isUserLoggedIn()) {
66+
navigateToChatActivity()
67+
return
68+
}
69+
70+
//setupEdgeToEdge()
71+
initializeViews()
72+
setupPrivacyTextView()
73+
configureGoogleSignIn()
74+
setupClickListeners()
75+
}
76+
77+
private fun setupPrivacyTextView() {
78+
val fullText = "By signing in, you agree to our Terms of Service and Privacy Policy"
79+
val spannableString = SpannableString(fullText)
80+
81+
// Find the positions of the links
82+
val termsStart = fullText.indexOf("Terms of Service")
83+
val termsEnd = termsStart + "Terms of Service".length
84+
val privacyStart = fullText.indexOf("Privacy Policy")
85+
val privacyEnd = privacyStart + "Privacy Policy".length
86+
val termsClickableSpan = object : ClickableSpan() {
87+
override fun onClick(widget: View) {
88+
openTermsOfService()
89+
}
90+
91+
override fun updateDrawState(ds: TextPaint) {
92+
super.updateDrawState(ds)
93+
ds.color = ContextCompat.getColor(this@LoginActivity, R.color.brown_700)
94+
ds.isUnderlineText = true
95+
}
96+
}
97+
98+
val privacyClickableSpan = object : ClickableSpan() {
99+
override fun onClick(widget: View) {
100+
openPrivacyPolicy()
101+
}
102+
103+
override fun updateDrawState(ds: TextPaint) {
104+
super.updateDrawState(ds)
105+
ds.color = ContextCompat.getColor(this@LoginActivity, R.color.brown_700)
106+
ds.isUnderlineText = true
107+
}
108+
}
109+
110+
// Apply the spans
111+
spannableString.setSpan(termsClickableSpan, termsStart, termsEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
112+
spannableString.setSpan(privacyClickableSpan, privacyStart, privacyEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
113+
114+
// Set the text and make it clickable
115+
textViewPrivacyPolicy.text = spannableString
116+
textViewPrivacyPolicy.movementMethod = LinkMovementMethod.getInstance()
117+
118+
// Optional: Remove the default link color highlighting
119+
textViewPrivacyPolicy.highlightColor = Color.TRANSPARENT
120+
121+
122+
}
123+
private fun openTermsOfService() {
124+
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://kharagedition.github.io/term-and-condition/tibetan-keyboard.html"))
125+
startActivity(intent)
126+
}
127+
128+
private fun openPrivacyPolicy() {
129+
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://kharagedition.github.io/privacy-policy/tibetan-keyboard"))
130+
startActivity(intent)
131+
}
132+
133+
private fun setupEdgeToEdge() {
134+
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main_container)) { v, insets ->
135+
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
136+
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
137+
insets
138+
}
139+
}
140+
141+
private fun initializeViews() {
142+
buttonGoogleSignIn = findViewById(R.id.buttonGoogleSignIn)
143+
progressIndicator = findViewById(R.id.progressIndicator)
144+
textViewPrivacyPolicy = findViewById(R.id.textViewPrivacy)
145+
}
146+
147+
private fun configureGoogleSignIn() {
148+
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
149+
.requestIdToken(getString(R.string.default_web_client_id))
150+
.requestEmail()
151+
.requestProfile()
152+
.build()
153+
154+
googleSignInClient = GoogleSignIn.getClient(this, gso)
155+
}
156+
157+
private fun setupClickListeners() {
158+
buttonGoogleSignIn.setOnClickListener {
159+
signInWithGoogle()
160+
}
161+
}
162+
163+
private fun signInWithGoogle() {
164+
showLoading()
165+
val signInIntent = googleSignInClient.signInIntent
166+
googleSignInLauncher.launch(signInIntent)
167+
}
168+
169+
private fun firebaseAuthWithGoogle(idToken: String) {
170+
val credential = GoogleAuthProvider.getCredential(idToken, null)
171+
auth.signInWithCredential(credential)
172+
.addOnCompleteListener(this) { task ->
173+
hideLoading()
174+
if (task.isSuccessful) {
175+
val user = auth.currentUser
176+
user?.let {
177+
// Save user login state
178+
userPreferences.saveUserLoginState(
179+
isLoggedIn = true,
180+
userId = it.uid,
181+
userName = it.displayName ?: "User",
182+
userEmail = it.email ?: "",
183+
userPhotoUrl = it.photoUrl?.toString() ?: ""
184+
)
185+
186+
Toast.makeText(this, "Welcome ${it.displayName}!", Toast.LENGTH_SHORT).show()
187+
navigateToChatActivity()
188+
}
189+
} else {
190+
Toast.makeText(
191+
this,
192+
"Authentication failed: ${task.exception?.message}",
193+
Toast.LENGTH_SHORT
194+
).show()
195+
}
196+
}
197+
}
198+
199+
private fun showLoading() {
200+
buttonGoogleSignIn.text = ""
201+
buttonGoogleSignIn.isEnabled = false
202+
progressIndicator.visibility = android.view.View.VISIBLE
203+
}
204+
205+
private fun hideLoading() {
206+
buttonGoogleSignIn.text = getString(R.string.sign_in_with_google)
207+
buttonGoogleSignIn.isEnabled = true
208+
progressIndicator.visibility = android.view.View.GONE
209+
}
210+
211+
private fun navigateToChatActivity() {
212+
val intent = Intent(this, ChatActivity::class.java)
213+
//intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
214+
startActivity(intent)
215+
finish()
216+
}
217+
218+
public override fun onStart() {
219+
super.onStart()
220+
// Check if user is signed in and update UI accordingly
221+
val currentUser = auth.currentUser
222+
if (currentUser != null && userPreferences.isUserLoggedIn()) {
223+
navigateToChatActivity()
224+
}
225+
}
226+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.kharagedition.tibetankeyboard
2+
3+
import android.content.Context
4+
import android.content.SharedPreferences
5+
6+
class UserPreferences(context: Context) {
7+
8+
private val sharedPreferences: SharedPreferences =
9+
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
10+
11+
companion object {
12+
private const val PREF_NAME = "user_preferences"
13+
private const val KEY_IS_LOGGED_IN = "is_logged_in"
14+
private const val KEY_USER_ID = "user_id"
15+
private const val KEY_USER_NAME = "user_name"
16+
private const val KEY_USER_EMAIL = "user_email"
17+
private const val KEY_USER_PHOTO_URL = "user_photo_url"
18+
private const val KEY_FIRST_TIME_USER = "first_time_user"
19+
}
20+
21+
fun saveUserLoginState(
22+
isLoggedIn: Boolean,
23+
userId: String = "",
24+
userName: String = "",
25+
userEmail: String = "",
26+
userPhotoUrl: String = ""
27+
) {
28+
sharedPreferences.edit().apply {
29+
putBoolean(KEY_IS_LOGGED_IN, isLoggedIn)
30+
putString(KEY_USER_ID, userId)
31+
putString(KEY_USER_NAME, userName)
32+
putString(KEY_USER_EMAIL, userEmail)
33+
putString(KEY_USER_PHOTO_URL, userPhotoUrl)
34+
if (isLoggedIn && isFirstTimeUser()) {
35+
putBoolean(KEY_FIRST_TIME_USER, false)
36+
}
37+
apply()
38+
}
39+
}
40+
41+
fun isUserLoggedIn(): Boolean {
42+
return sharedPreferences.getBoolean(KEY_IS_LOGGED_IN, false)
43+
}
44+
45+
fun getUserId(): String {
46+
return sharedPreferences.getString(KEY_USER_ID, "") ?: ""
47+
}
48+
49+
fun getUserName(): String {
50+
return sharedPreferences.getString(KEY_USER_NAME, "") ?: ""
51+
}
52+
53+
fun getUserEmail(): String {
54+
return sharedPreferences.getString(KEY_USER_EMAIL, "") ?: ""
55+
}
56+
57+
fun getUserPhotoUrl(): String {
58+
return sharedPreferences.getString(KEY_USER_PHOTO_URL, "") ?: ""
59+
}
60+
61+
fun isFirstTimeUser(): Boolean {
62+
return sharedPreferences.getBoolean(KEY_FIRST_TIME_USER, true)
63+
}
64+
65+
fun clearUserData() {
66+
sharedPreferences.edit().apply {
67+
putBoolean(KEY_IS_LOGGED_IN, false)
68+
putString(KEY_USER_ID, "")
69+
putString(KEY_USER_NAME, "")
70+
putString(KEY_USER_EMAIL, "")
71+
putString(KEY_USER_PHOTO_URL, "")
72+
apply()
73+
}
74+
}
75+
}

app/src/main/java/com/kharagedition/tibetankeyboard/chat/ChatViewModel.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,8 @@ class ChatViewModel : ViewModel() {
7474
_messages.value = listOf(welcomeMessage)
7575
}
7676
}
77+
78+
fun clearMessages() {
79+
_messages.value = emptyList()
80+
}
7781
}

0 commit comments

Comments
 (0)