From c720446cdb97029a25f90a5b8442b49d37eb1ea8 Mon Sep 17 00:00:00 2001 From: rudrankbasant Date: Fri, 8 Sep 2023 21:18:23 +0530 Subject: [PATCH 01/87] feat: added community apis --- .../network/api/community/APICommunity.kt | 62 +++++ .../api/community/APICommunityRestClient.kt | 250 ++++++++++++++++++ .../{api => network/api/events}/APIEvents.kt | 5 +- .../api/events}/ApiEventRestClient.kt | 2 +- .../api/events}/RetrofitEventListener.kt | 3 +- 5 files changed, 319 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/com/dscvit/vitty/network/api/community/APICommunity.kt create mode 100644 app/src/main/java/com/dscvit/vitty/network/api/community/APICommunityRestClient.kt rename app/src/main/java/com/dscvit/vitty/{api => network/api/events}/APIEvents.kt (88%) rename app/src/main/java/com/dscvit/vitty/{api => network/api/events}/ApiEventRestClient.kt (96%) rename app/src/main/java/com/dscvit/vitty/{api => network/api/events}/RetrofitEventListener.kt (83%) diff --git a/app/src/main/java/com/dscvit/vitty/network/api/community/APICommunity.kt b/app/src/main/java/com/dscvit/vitty/network/api/community/APICommunity.kt new file mode 100644 index 0000000..a9dcd1f --- /dev/null +++ b/app/src/main/java/com/dscvit/vitty/network/api/community/APICommunity.kt @@ -0,0 +1,62 @@ +package com.dscvit.vitty.network.api.community + +import com.dscvit.vitty.network.api.community.requests.AuthRequestBody +import com.dscvit.vitty.network.api.community.requests.UsernameRequestBody +import com.dscvit.vitty.network.api.community.responses.requests.RequestsResponse +import com.dscvit.vitty.network.api.community.responses.requests.RequestsResponseItem +import com.dscvit.vitty.network.api.community.responses.timetable.TimetableResponse +import com.dscvit.vitty.network.api.community.responses.user.PostResponse +import com.dscvit.vitty.network.api.community.responses.user.FriendResponse +import com.dscvit.vitty.network.api.community.responses.user.SignInResponse +import com.dscvit.vitty.network.api.community.responses.user.UserResponse +import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Param +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.DELETE +import retrofit2.http.FormUrlEncoded +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.Headers +import retrofit2.http.POST +import retrofit2.http.Path +import retrofit2.http.Query +import retrofit2.http.QueryMap + +interface APICommunity { + + @Headers("Content-Type: application/json") + @POST("/api/v2/auth/check-username") + fun checkUsername(@Body body: UsernameRequestBody) : Call + + @Headers("Content-Type: application/json") + @POST("/api/v2/auth/firebase/") + fun signInInfo(@Body body: AuthRequestBody ) : Call + + @GET("/api/v2/users/{username}") + fun getUser(@Header("Authorization") authToken: String, @Path("username") username: String): Call + + @GET("/api/v2/friends/{username}/") + fun getFriendList(@Header("Authorization") authToken: String,@Path("username") username: String): Call + + @GET("/api/v2/users/search") + fun searchUsers(@Header("Authorization") authToken: String,@Query("query") query: String): Call> + + @GET("/api/v2/requests/") + fun getFriendRequests(@Header("Authorization") authToken: String): Call + + @GET("/api/v2/users/suggested/") + fun getSuggestedFriends(@Header("Authorization") authToken: String): Call> + + @POST("/api/v2/requests/{username}/send") + fun sendRequest(@Header("Authorization") authToken: String,@Path("username") username: String): Call + + @POST("/api/v2/requests/{username}/accept/") + fun acceptRequest(@Header("Authorization") authToken: String,@Path("username") username: String): Call + + @POST("/api/v2/requests/{username}/decline/") + fun declineRequest(@Header("Authorization") authToken: String,@Path("username") username: String): Call + + + @DELETE("/api/v2/friends/{username}/") + fun deleteFriend(@Header("Authorization") authToken: String,@Path("username") username: String): Call +} \ No newline at end of file diff --git a/app/src/main/java/com/dscvit/vitty/network/api/community/APICommunityRestClient.kt b/app/src/main/java/com/dscvit/vitty/network/api/community/APICommunityRestClient.kt new file mode 100644 index 0000000..a37ade3 --- /dev/null +++ b/app/src/main/java/com/dscvit/vitty/network/api/community/APICommunityRestClient.kt @@ -0,0 +1,250 @@ +package com.dscvit.vitty.network.api.community + +import com.dscvit.vitty.network.api.community.requests.AuthRequestBody +import com.dscvit.vitty.network.api.community.requests.UsernameRequestBody +import com.dscvit.vitty.network.api.community.responses.requests.RequestsResponse +import com.dscvit.vitty.network.api.community.responses.requests.RequestsResponseItem +import com.dscvit.vitty.network.api.community.responses.user.FriendResponse +import com.dscvit.vitty.network.api.community.responses.user.PostResponse +import com.dscvit.vitty.network.api.community.responses.user.SignInResponse +import com.dscvit.vitty.network.api.community.responses.user.UserResponse +import com.google.gson.Gson +import com.google.gson.JsonSyntaxException +import org.json.JSONException +import org.json.JSONObject +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import timber.log.Timber + +class APICommunityRestClient { + + + + companion object { + val instance = APICommunityRestClient() + } + + private var mApiUser: APICommunity? = null + val retrofit = CommunityNetworkClient.retrofitClientCommunity + + + fun signInWithUsernameRegNo( + username: String, + regno: String, + uuid: String, + retrofitCommunitySignInListener: RetrofitCommunitySignInListener, + retrofitSelfUserListener: RetrofitSelfUserListener + ){ + + mApiUser = retrofit.create(APICommunity::class.java) + + + + val requestBody = AuthRequestBody( + reg_no = regno, + username = username, + uuid = uuid + ) + + val apiSignInCall = mApiUser!!.signInInfo(requestBody) + + apiSignInCall.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + retrofitCommunitySignInListener.onSuccess(call, response.body()) + val token = response.body()?.token.toString() + val res_username = response.body()?.username.toString() + + getUserWithTimeTable( token, res_username, retrofitSelfUserListener,) + + } + + override fun onFailure(call: Call, t: Throwable) { + retrofitCommunitySignInListener.onError(call, t) + + } + }) + } + + fun getUserWithTimeTable(token: String, username: String, retrofitSelfUserListener: RetrofitSelfUserListener) { + val bearerToken = "Bearer $token" + + mApiUser = retrofit.create(APICommunity::class.java) + val apiUserCall = mApiUser!!.getUser(bearerToken, username) + apiUserCall.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + retrofitSelfUserListener.onSuccess(call, response.body()) + } + + override fun onFailure(call: Call, t: Throwable) { + retrofitSelfUserListener.onError(call, t) + } + }) + + } + + + fun getFriendList(token: String, username: String, retrofitFriendListListener: RetrofitFriendListListener ){ + val bearerToken = "Bearer $token" + + mApiUser = retrofit.create(APICommunity::class.java) + val apiFriendListCall = mApiUser!!.getFriendList(bearerToken, username) + apiFriendListCall.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + retrofitFriendListListener.onSuccess(call, response.body()) + } + + override fun onFailure(call: Call, t: Throwable) { + retrofitFriendListListener.onError(call, t) + } + }) + } + + fun getSearchResult( + token: String, + query: String, + retrofitSearchResultListener: RetrofitSearchResultListener + ){ + val bearerToken = "Bearer $token" + + mApiUser = retrofit.create(APICommunity::class.java) + val apiSearchResultCall = mApiUser!!.searchUsers(bearerToken,query) + apiSearchResultCall.enqueue(object : Callback> { + override fun onResponse(call: Call>, response: Response>) { + Timber.d("SearchResult4: $response") + retrofitSearchResultListener.onSuccess(call, response.body()) + } + + override fun onFailure(call: Call>, t: Throwable) { + retrofitSearchResultListener.onError(call, t) + } + }) + } + + fun getSuggestedFriends( + token: String, + retrofitSearchResultListener: RetrofitSearchResultListener + ){ + val bearerToken = "Bearer $token" + + mApiUser = retrofit.create(APICommunity::class.java) + val apiSuggestedResultCall = mApiUser!!.getSuggestedFriends(bearerToken) + apiSuggestedResultCall.enqueue(object : Callback> { + override fun onResponse(call: Call>, response: Response>) { + Timber.d("SearchResult4: $response") + retrofitSearchResultListener.onSuccess(call, response.body()) + } + + override fun onFailure(call: Call>, t: Throwable) { + retrofitSearchResultListener.onError(call, t) + } + }) + } + + fun getFriendRequest( + token: String, + retrofitFriendRequestListener: RetrofitFriendRequestListener + ){ + val bearerToken = "Bearer $token" + Timber.d("FriendReqToken--: $bearerToken") + mApiUser = retrofit.create(APICommunity::class.java) + val apiFriendRequestCall = mApiUser!!.getFriendRequests(bearerToken) + apiFriendRequestCall.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + Timber.d("FriendRequest--: $response") + retrofitFriendRequestListener.onSuccess(call, response.body()) + } + + override fun onFailure(call: Call, t: Throwable) { + Timber.d("FriendRequestError--: ${t.message}") + retrofitFriendRequestListener.onError(call, t) + } + } + ) + } + + fun acceptRequest(token: String, username: String, retrofitUserActionListener: RetrofitUserActionListener) { + val bearerToken = "Bearer $token" + + mApiUser = retrofit.create(APICommunity::class.java) + val apiAcceptRequestCall = mApiUser!!.acceptRequest(bearerToken, username) + apiAcceptRequestCall.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + retrofitUserActionListener.onSuccess(call, response.body()) + } + + override fun onFailure(call: Call, t: Throwable) { + retrofitUserActionListener.onError(call, t) + } + }) + + } + + fun rejectRequest(token: String, username: String, retrofitUserActionListener: RetrofitUserActionListener) { + val bearerToken = "Bearer $token" + + mApiUser = retrofit.create(APICommunity::class.java) + val apiRejectRequestCall = mApiUser!!.declineRequest(bearerToken, username) + apiRejectRequestCall.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + retrofitUserActionListener.onSuccess(call, response.body()) + } + + override fun onFailure(call: Call, t: Throwable) { + retrofitUserActionListener.onError(call, t) + } + }) + + } + + fun sendRequest(token: String, username: String, retrofitUserActionListener: RetrofitUserActionListener) { + val bearerToken = "Bearer $token" + + mApiUser = retrofit.create(APICommunity::class.java) + val apiSendRequestCall = mApiUser!!.sendRequest(bearerToken, username) + apiSendRequestCall.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + retrofitUserActionListener.onSuccess(call, response.body()) + } + + override fun onFailure(call: Call, t: Throwable) { + retrofitUserActionListener.onError(call, t) + } + }) + + } + + fun checkUsername(username: String, retrofitUserActionListener: RetrofitUserActionListener) { + mApiUser = retrofit.create(APICommunity::class.java) + val usernameRequestBody = UsernameRequestBody(username) + val apiCheckUsernameCall = mApiUser!!.checkUsername(usernameRequestBody) + apiCheckUsernameCall.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + if(response.isSuccessful) { + retrofitUserActionListener.onSuccess(call, response.body()) + } else { + val gson = Gson() + val errorString = response.errorBody()?.string() + Timber.d("ResponseV: $errorString") + try { + val errorResponse = gson.fromJson(errorString, PostResponse::class.java) + retrofitUserActionListener.onSuccess(call, errorResponse) + } catch (e: JsonSyntaxException) { + // Handle any JSON parsing errors. + val errorMessage = "Username is not valid/available." + retrofitUserActionListener.onSuccess(call, PostResponse(errorMessage)) + } + } + } + + override fun onFailure(call: Call, t: Throwable) { + Timber.d("ErrorV: ${t.message}") + retrofitUserActionListener.onError(call, t) + } + }) + + + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/dscvit/vitty/api/APIEvents.kt b/app/src/main/java/com/dscvit/vitty/network/api/events/APIEvents.kt similarity index 88% rename from app/src/main/java/com/dscvit/vitty/api/APIEvents.kt rename to app/src/main/java/com/dscvit/vitty/network/api/events/APIEvents.kt index 3f02fdc..34389fd 100644 --- a/app/src/main/java/com/dscvit/vitty/api/APIEvents.kt +++ b/app/src/main/java/com/dscvit/vitty/network/api/events/APIEvents.kt @@ -1,4 +1,4 @@ -package com.dscvit.vitty.api +package com.dscvit.vitty.network.api.events import com.dscvit.vitty.model.EventDetails import retrofit2.Call import retrofit2.http.GET @@ -10,4 +10,7 @@ interface APIEvents { // https://reqres.in/api/users?page=1 @GET("event?") fun getEvents(@QueryMap options: Map): Call + + + } diff --git a/app/src/main/java/com/dscvit/vitty/api/ApiEventRestClient.kt b/app/src/main/java/com/dscvit/vitty/network/api/events/ApiEventRestClient.kt similarity index 96% rename from app/src/main/java/com/dscvit/vitty/api/ApiEventRestClient.kt rename to app/src/main/java/com/dscvit/vitty/network/api/events/ApiEventRestClient.kt index 08b7b00..31f12f3 100644 --- a/app/src/main/java/com/dscvit/vitty/api/ApiEventRestClient.kt +++ b/app/src/main/java/com/dscvit/vitty/network/api/events/ApiEventRestClient.kt @@ -1,4 +1,4 @@ -package com.dscvit.vitty.api +package com.dscvit.vitty.network.api.events import com.dscvit.vitty.model.EventDetails import retrofit2.Call diff --git a/app/src/main/java/com/dscvit/vitty/api/RetrofitEventListener.kt b/app/src/main/java/com/dscvit/vitty/network/api/events/RetrofitEventListener.kt similarity index 83% rename from app/src/main/java/com/dscvit/vitty/api/RetrofitEventListener.kt rename to app/src/main/java/com/dscvit/vitty/network/api/events/RetrofitEventListener.kt index cb76aec..a2935a4 100644 --- a/app/src/main/java/com/dscvit/vitty/api/RetrofitEventListener.kt +++ b/app/src/main/java/com/dscvit/vitty/network/api/events/RetrofitEventListener.kt @@ -1,4 +1,4 @@ -package com.dscvit.vitty.api +package com.dscvit.vitty.network.api.events import com.dscvit.vitty.model.EventDetails import retrofit2.Call @@ -7,3 +7,4 @@ interface RetrofitEventListener { fun onSuccess(call: Call?, response: EventDetails?) fun onError(call: Call?, t: Throwable?) } + From dfd373cfe0179be11c46f2e23596002f0c807f4b Mon Sep 17 00:00:00 2001 From: rudrankbasant Date: Fri, 8 Sep 2023 21:20:31 +0530 Subject: [PATCH 02/87] feat: auth flow --- app/src/main/AndroidManifest.xml | 4 + .../dscvit/vitty/activity/AddInfoActivity.kt | 307 ++++++++++++++++++ .../com/dscvit/vitty/activity/AuthActivity.kt | 92 +++++- .../vitty/activity/InstructionsActivity.kt | 37 ++- .../api/community/CommunityNetworkClient.kt | 28 ++ .../community/RetrofitCommunityListener.kt | 43 +++ .../api/community/requests/AuthRequestBody.kt | 7 + .../community/requests/UsernameRequestBody.kt | 5 + .../community/responses/user/PostResponse.kt | 5 + .../responses/user/SignInResponse.kt | 9 + .../community/responses/user/UserResponse.kt | 14 + .../api/events}/NetworkClient.kt | 2 +- .../com/dscvit/vitty/ui/auth/AuthViewModel.kt | 70 ++++ .../vitty/ui/events/VITEventsViewModel.kt | 4 +- app/src/main/res/drawable/et_style.xml | 8 + app/src/main/res/layout/activity_add_info.xml | 146 +++++++++ 16 files changed, 768 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/com/dscvit/vitty/activity/AddInfoActivity.kt create mode 100644 app/src/main/java/com/dscvit/vitty/network/api/community/CommunityNetworkClient.kt create mode 100644 app/src/main/java/com/dscvit/vitty/network/api/community/RetrofitCommunityListener.kt create mode 100644 app/src/main/java/com/dscvit/vitty/network/api/community/requests/AuthRequestBody.kt create mode 100644 app/src/main/java/com/dscvit/vitty/network/api/community/requests/UsernameRequestBody.kt create mode 100644 app/src/main/java/com/dscvit/vitty/network/api/community/responses/user/PostResponse.kt create mode 100644 app/src/main/java/com/dscvit/vitty/network/api/community/responses/user/SignInResponse.kt create mode 100644 app/src/main/java/com/dscvit/vitty/network/api/community/responses/user/UserResponse.kt rename app/src/main/java/com/dscvit/vitty/{api => network/api/events}/NetworkClient.kt (95%) create mode 100644 app/src/main/java/com/dscvit/vitty/ui/auth/AuthViewModel.kt create mode 100644 app/src/main/res/drawable/et_style.xml create mode 100644 app/src/main/res/layout/activity_add_info.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a01a9fc..c291837 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,9 +21,13 @@ android:fullBackupContent="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" + android:usesCleartextTraffic="true" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.VITTY"> + diff --git a/app/src/main/java/com/dscvit/vitty/activity/AddInfoActivity.kt b/app/src/main/java/com/dscvit/vitty/activity/AddInfoActivity.kt new file mode 100644 index 0000000..00bb82f --- /dev/null +++ b/app/src/main/java/com/dscvit/vitty/activity/AddInfoActivity.kt @@ -0,0 +1,307 @@ +package com.dscvit.vitty.activity + +import android.app.Activity +import android.app.AlarmManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.content.SharedPreferences +import android.os.Build +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.os.PowerManager +import android.provider.Settings +import android.text.Editable +import android.text.TextWatcher +import android.view.View +import android.widget.Toast +import androidx.core.content.edit +import androidx.databinding.DataBindingUtil +import androidx.lifecycle.ViewModelProvider +import com.dscvit.vitty.BuildConfig +import com.dscvit.vitty.R +import com.dscvit.vitty.databinding.ActivityAddInfoBinding +import com.dscvit.vitty.receiver.AlarmReceiver +import com.dscvit.vitty.ui.auth.AuthViewModel +import com.dscvit.vitty.util.ArraySaverLoader +import com.dscvit.vitty.util.Constants +import com.dscvit.vitty.util.LogoutHelper +import com.dscvit.vitty.util.NotificationHelper +import com.dscvit.vitty.util.NotificationHelper.setAlarm +import com.dscvit.vitty.util.UtilFunctions +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.firestore.FirebaseFirestore +import com.google.firebase.firestore.Source +import timber.log.Timber +import java.util.ArrayList +import java.util.Date + +class AddInfoActivity : AppCompatActivity() { + + private lateinit var binding: ActivityAddInfoBinding + private lateinit var authViewModel: AuthViewModel + private lateinit var prefs: SharedPreferences + private val db = FirebaseFirestore.getInstance() + private val days = + listOf("monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday") + private var uid = "" + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = DataBindingUtil.setContentView(this, R.layout.activity_add_info) + authViewModel = ViewModelProvider(this)[AuthViewModel::class.java] + prefs = getSharedPreferences(Constants.USER_INFO, Context.MODE_PRIVATE) + uid = prefs.getString(Constants.UID, "").toString() + setupToolbar() + setGDSCVITChannel() + + binding.continueButton.setOnClickListener { + setupContinueButton() + } + + authViewModel.signInResponse.observe(this) { + if (it != null) { + prefs.edit().putString(Constants.COMMUNITY_TOKEN, it.token).apply() + prefs.edit().putString(Constants.COMMUNITY_NAME, it.name).apply() + prefs.edit().putString(Constants.COMMUNITY_PICTURE, it.picture).apply() + }else{ + Toast.makeText(this, R.string.something_went_wrong, Toast.LENGTH_LONG).show() + binding.loadingView.visibility = View.GONE + } + } + + authViewModel.user.observe(this){ + if(it!=null){ + val timetableDays = it.timetable?.data + if( true /*!timetableDays.Monday.isNullOrEmpty() || !timetableDays.Tuesday.isNullOrEmpty() || !timetableDays.Wednesday.isNullOrEmpty() || !timetableDays.Thursday.isNullOrEmpty() || !timetableDays.Friday.isNullOrEmpty()*/) { + binding.loadingView.visibility = View.GONE + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createNotificationChannels() + } else { + tellUpdated() + } + prefs.edit().putBoolean(Constants.COMMUNITY_TIMETABLE_AVAILABLE, true).apply() + }else{ + val intent = Intent(this, InstructionsActivity::class.java) + binding.loadingView.visibility = View.GONE + startActivity(intent) + finish() + } + } + } + + + binding.etUsername.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + val username = s.toString().trim() + authViewModel.checkUsername(username) + } + + override fun afterTextChanged(s: Editable?) { + + } + }) + + authViewModel.usernameValidity.observe(this) { + Timber.d("Validity: ${it}") + if (it != null) { + binding.usernameValidity.visibility = View.VISIBLE + binding.usernameValidity.text = it.detail + if (it.detail == "Username is valid") { + binding.usernameValidity.setTextColor(getColor(R.color.white)) + } else { + binding.usernameValidity.setTextColor(getColor(R.color.red)) + } + } + } + + } + + + + private fun setupContinueButton() { + binding.loadingView.visibility = View.VISIBLE + val uuid = prefs.getString(Constants.UID, null) + val username =binding.etUsername.text.toString().trim { it <= ' ' } + val regno = binding.etRegno.text.toString().uppercase().trim { it <= ' ' } + val regexPattern = Regex("^[0-9]{2}[a-zA-Z]{3}[0-9]{4}$") + if (username.isEmpty() || regno.isEmpty()) { + Toast.makeText(this, getString(R.string.fill_all_fields), Toast.LENGTH_LONG).show() + binding.loadingView.visibility = View.GONE + } else if (!regexPattern.matches(regno)) { + Toast.makeText(this, getString(R.string.invalid_regno), Toast.LENGTH_LONG).show() + binding.loadingView.visibility = View.GONE + } else { + if (uuid != null) { + prefs.edit().putString(Constants.COMMUNITY_USERNAME, username).apply() + prefs.edit().putString(Constants.COMMUNITY_REGNO, regno).apply() + authViewModel.signInAndGetTimeTable(username, regno, uuid) + }else{ + Toast.makeText(this, getString(R.string.something_went_wrong), Toast.LENGTH_LONG).show() + binding.loadingView.visibility = View.GONE + } + } + + + } + private fun setupToolbar() { + binding.addInfoToolbar.setOnMenuItemClickListener { menuItem -> + when (menuItem.itemId) { + R.id.close -> { + LogoutHelper.logout(this, this as Activity, prefs) + true + } + else -> false + } + } + } + + private fun createNotificationChannels() { + binding.loadingView.visibility = View.VISIBLE + setNotificationGroup() + val notifChannels = ArraySaverLoader.loadArray(Constants.NOTIFICATION_CHANNELS, this) + for (notifChannel in notifChannels) { + if (notifChannel != null) { + NotificationHelper.deleteNotificationChannel(this, notifChannel.toString()) + } + } + val newNotifChannels: ArrayList = ArrayList() + for (day in days) { + db.collection("users") + .document(uid) + .collection("timetable") + .document(day) + .collection("periods") + .get(Source.SERVER) + .addOnSuccessListener { result -> + for (document in result) { + var cn = document.getString("courseName") + cn = if (cn.isNullOrEmpty()) "Default" else cn + val cc = document.getString("courseCode") + NotificationHelper.createNotificationChannel( + this, + cn, + "Course Code: $cc", + Constants.GROUP_ID + ) + newNotifChannels.add(cn) + Timber.d(cn) + } + ArraySaverLoader.saveArray( + newNotifChannels, + Constants.NOTIFICATION_CHANNELS, + this + ) + + if (day == "sunday") + tellUpdated() + } + .addOnFailureListener { e -> + Timber.d("Error: $e") + } + } + } + + private fun tellUpdated() { + prefs.edit().putInt(Constants.TIMETABLE_AVAILABLE, 1).apply() + prefs.edit().putInt(Constants.UPDATE, 0).apply() + val updated = hashMapOf( + "isTimetableAvailable" to true, + "isUpdated" to false + ) + db.collection("users") + .document(uid) + .set(updated) + .addOnSuccessListener { + setAlarm() + UtilFunctions.reloadWidgets(this) + val pm: PowerManager = getSystemService(Context.POWER_SERVICE) as PowerManager + if (!pm.isIgnoringBatteryOptimizations(packageName)) { + Toast.makeText( + this, + "Please turn off the Battery Optimization Settings for VITTY to receive notifications on time.", + Toast.LENGTH_LONG + ).show() + val pmIntent = Intent() + pmIntent.action = Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS + startActivity(pmIntent) + } else { + val intent = Intent(this, HomeActivity::class.java) + startActivity(intent) + finish() + } + } + .addOnFailureListener { e -> + Timber.d("Error: $e") + } + } + + + private fun setGDSCVITChannel() { + if (!prefs.getBoolean("gdscvitChannelCreated", false)) { + NotificationHelper.createNotificationGroup( + this, + getString(R.string.gdscvit), + Constants.GROUP_ID_2 + ) + NotificationHelper.createNotificationChannel( + this, + getString(R.string.default_notification_channel_name), + "Notifications from GDSC VIT", + Constants.GROUP_ID_2 + ) + prefs.edit { + putBoolean("gdscvitChannelCreated", true) + apply() + } + } + } + + private fun setNotificationGroup() { + if (!prefs.getBoolean("groupCreated", false)) { + NotificationHelper.createNotificationGroup( + this, + getString(R.string.notif_group), + Constants.GROUP_ID + ) + prefs.edit { + putBoolean("groupCreated", true) + apply() + } + } + } + + private fun setAlarm() { + if (!prefs.getBoolean(Constants.EXAM_MODE, false)) { + if (prefs.getInt(Constants.VERSION_CODE, 0) != BuildConfig.VERSION_CODE) { + val intent = Intent(this, AlarmReceiver::class.java) + + val pendingIntent = + PendingIntent.getBroadcast( + this, Constants.ALARM_INTENT, intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + val alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager + + val date = Date().time + + alarmManager.setRepeating( + AlarmManager.RTC_WAKEUP, + date, + (1000 * 60 * Constants.NOTIF_DELAY).toLong(), + pendingIntent + ) + + prefs.edit { + putInt(Constants.VERSION_CODE, BuildConfig.VERSION_CODE) + apply() + } + } + } + } + +} + + diff --git a/app/src/main/java/com/dscvit/vitty/activity/AuthActivity.kt b/app/src/main/java/com/dscvit/vitty/activity/AuthActivity.kt index c984561..7990b53 100755 --- a/app/src/main/java/com/dscvit/vitty/activity/AuthActivity.kt +++ b/app/src/main/java/com/dscvit/vitty/activity/AuthActivity.kt @@ -3,18 +3,30 @@ package com.dscvit.vitty.activity import android.content.Context import android.content.Intent import android.content.SharedPreferences +import android.os.Build import android.os.Bundle +import android.os.PowerManager +import android.provider.Settings +import android.util.Log import android.view.View import android.widget.Toast import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.edit import androidx.databinding.DataBindingUtil +import androidx.lifecycle.ViewModelProvider import androidx.viewpager2.widget.ViewPager2 import com.dscvit.vitty.R import com.dscvit.vitty.adapter.IntroAdapter import com.dscvit.vitty.databinding.ActivityAuthBinding +import com.dscvit.vitty.ui.auth.AuthViewModel +import com.dscvit.vitty.util.ArraySaverLoader +import com.dscvit.vitty.util.Constants import com.dscvit.vitty.util.Constants.TOKEN import com.dscvit.vitty.util.Constants.UID import com.dscvit.vitty.util.Constants.USER_INFO +import com.dscvit.vitty.util.NotificationHelper +import com.dscvit.vitty.util.NotificationHelper.setAlarm +import com.dscvit.vitty.util.UtilFunctions import com.google.android.gms.auth.api.signin.GoogleSignIn import com.google.android.gms.auth.api.signin.GoogleSignInAccount import com.google.android.gms.auth.api.signin.GoogleSignInClient @@ -24,6 +36,9 @@ import com.google.android.gms.tasks.Task import com.google.android.material.tabs.TabLayoutMediator import com.google.firebase.auth.FirebaseAuth import com.google.firebase.auth.GoogleAuthProvider +import com.google.firebase.firestore.Source +import timber.log.Timber +import java.util.ArrayList class AuthActivity : AppCompatActivity() { @@ -34,6 +49,7 @@ class AuthActivity : AppCompatActivity() { private lateinit var mGoogleSignInOptions: GoogleSignInOptions private lateinit var firebaseAuth: FirebaseAuth private lateinit var sharedPref: SharedPreferences + private lateinit var authViewModel: AuthViewModel private val pages = listOf("○", "○", "○") private var loginClick = false @@ -43,18 +59,37 @@ class AuthActivity : AppCompatActivity() { binding = DataBindingUtil.setContentView(this, R.layout.activity_auth) firebaseAuth = FirebaseAuth.getInstance() sharedPref = getSharedPreferences(USER_INFO, Context.MODE_PRIVATE) + authViewModel = ViewModelProvider(this)[AuthViewModel::class.java] configureGoogleSignIn() setupUI() } override fun onStart() { super.onStart() + sharedPref = getSharedPreferences(USER_INFO, Context.MODE_PRIVATE) + val isTimeTableAvailable = sharedPref.getBoolean(Constants.COMMUNITY_TIMETABLE_AVAILABLE, false) + val token = sharedPref.getString(Constants.COMMUNITY_TOKEN, null) + val username = sharedPref.getString(Constants.COMMUNITY_USERNAME, null) + val regno = sharedPref.getString(Constants.COMMUNITY_REGNO, null) val user = FirebaseAuth.getInstance().currentUser - if (user != null) { - val intent = Intent(this, InstructionsActivity::class.java) + Timber.d("isTimeTableAvailable: $isTimeTableAvailable token: $token username: $username regno: $regno") + if (isTimeTableAvailable) { + val intent = Intent(this, HomeActivity::class.java) startActivity(intent) finish() + }else if(token!=null && username!=null && regno!=null) { + val intent = Intent(this, InstructionsActivity::class.java) + startActivity(intent) + finish() + }else{ + if (user != null) { + val intent = Intent(this, AddInfoActivity::class.java) + startActivity(intent) + finish() + } } + + } private fun configureGoogleSignIn() { @@ -138,7 +173,7 @@ class AuthActivity : AppCompatActivity() { firebaseAuthWithGoogle(account) } } catch (e: ApiException) { - logoutFailed() + logoutFailed() } } } @@ -150,16 +185,59 @@ class AuthActivity : AppCompatActivity() { loginClick = true val uid = firebaseAuth.currentUser?.uid saveInfo(acct.idToken, uid) - val intent = Intent(this, InstructionsActivity::class.java) - binding.loadingView.visibility = View.GONE - startActivity(intent) - finish() + authViewModel.signInAndGetTimeTable("","",uid?: "") + leadToNextPage() + + } else { logoutFailed() } } } + private fun leadToNextPage(){ + + + + authViewModel.signInResponse.observe(this) { + if (it != null) { + Timber.d("here--"+it.toString()) + sharedPref.edit().putString(Constants.COMMUNITY_USERNAME, it.username).apply() + sharedPref.edit().putString(Constants.COMMUNITY_TOKEN, it.token).apply() + sharedPref.edit().putString(Constants.COMMUNITY_NAME, it.name).apply() + sharedPref.edit().putString(Constants.COMMUNITY_PICTURE, it.picture).apply() + }else{ + val intent = Intent(this, AddInfoActivity::class.java) + binding.loadingView.visibility = View.GONE + startActivity(intent) + finish() + } + } + + authViewModel.user.observe(this){ + if(it!=null){ + val timetableDays = it.timetable?.data + if( true /*!timetableDays.Monday.isNullOrEmpty() || !timetableDays.Tuesday.isNullOrEmpty() || !timetableDays.Wednesday.isNullOrEmpty() || !timetableDays.Thursday.isNullOrEmpty() || !timetableDays.Friday.isNullOrEmpty()*/) { + sharedPref.edit().putBoolean(Constants.COMMUNITY_TIMETABLE_AVAILABLE, true).apply() + val intent = Intent(this, HomeActivity::class.java) + startActivity(intent) + finish() + binding.loadingView.visibility = View.GONE + }else{ + val intent = Intent(this, InstructionsActivity::class.java) + startActivity(intent) + finish() + binding.loadingView.visibility = View.GONE + } + } + } + } + + + + + + override fun onBackPressed() { binding.apply { if (introPager.currentItem == 0 || loginClick) { diff --git a/app/src/main/java/com/dscvit/vitty/activity/InstructionsActivity.kt b/app/src/main/java/com/dscvit/vitty/activity/InstructionsActivity.kt index d23fc02..97d6059 100755 --- a/app/src/main/java/com/dscvit/vitty/activity/InstructionsActivity.kt +++ b/app/src/main/java/com/dscvit/vitty/activity/InstructionsActivity.kt @@ -4,21 +4,28 @@ import android.app.Activity import android.app.AlarmManager import android.app.PendingIntent import android.content.Context +import android.content.Context.ALARM_SERVICE import android.content.Intent import android.content.SharedPreferences import android.os.Build import android.os.Bundle import android.os.PowerManager import android.provider.Settings +import android.util.Log +import android.view.Gravity.apply import android.view.View import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.core.content.edit import androidx.databinding.DataBindingUtil +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider import com.dscvit.vitty.BuildConfig import com.dscvit.vitty.R import com.dscvit.vitty.databinding.ActivityInstructionsBinding import com.dscvit.vitty.receiver.AlarmReceiver +import com.dscvit.vitty.ui.auth.AuthViewModel +import com.dscvit.vitty.ui.schedule.ScheduleViewModel import com.dscvit.vitty.util.ArraySaverLoader.loadArray import com.dscvit.vitty.util.ArraySaverLoader.saveArray import com.dscvit.vitty.util.Constants.ALARM_INTENT @@ -38,11 +45,13 @@ import com.dscvit.vitty.util.UtilFunctions import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.Source import timber.log.Timber +import java.util.ArrayList import java.util.Date class InstructionsActivity : AppCompatActivity() { private lateinit var binding: ActivityInstructionsBinding + private lateinit var authViewModel: AuthViewModel private val days = listOf("monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday") private lateinit var prefs: SharedPreferences @@ -54,7 +63,7 @@ class InstructionsActivity : AppCompatActivity() { binding = DataBindingUtil.setContentView(this, R.layout.activity_instructions) prefs = getSharedPreferences(USER_INFO, 0) uid = prefs.getString(UID, "").toString() - + authViewModel = ViewModelProvider(this)[AuthViewModel::class.java] setupToolbar() setGDSCVITChannel() @@ -80,7 +89,29 @@ class InstructionsActivity : AppCompatActivity() { private fun setupDoneButton() { binding.loadingView.visibility = View.VISIBLE - db.collection("users") + + + authViewModel.user.observe(this) { + if (it != null) { + val timetableDays = it.timetable?.data + if( !timetableDays?.Monday.isNullOrEmpty() || !timetableDays?.Tuesday.isNullOrEmpty() || !timetableDays?.Wednesday.isNullOrEmpty() || !timetableDays?.Thursday.isNullOrEmpty() || !timetableDays?.Friday.isNullOrEmpty()){ + binding.loadingView.visibility = View.GONE + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createNotificationChannels() + } else { + tellUpdated() + } + }else{ + binding.loadingView.visibility = View.GONE + Toast.makeText(this, getString(R.string.follow_instructions), Toast.LENGTH_LONG) + .show() + } + + } + } + + + /*db.collection("users") .document(uid) .get() .addOnSuccessListener { document -> @@ -95,7 +126,7 @@ class InstructionsActivity : AppCompatActivity() { Toast.makeText(this, getString(R.string.follow_instructions), Toast.LENGTH_LONG) .show() } - } + }*/ } private fun createNotificationChannels() { diff --git a/app/src/main/java/com/dscvit/vitty/network/api/community/CommunityNetworkClient.kt b/app/src/main/java/com/dscvit/vitty/network/api/community/CommunityNetworkClient.kt new file mode 100644 index 0000000..3e66894 --- /dev/null +++ b/app/src/main/java/com/dscvit/vitty/network/api/community/CommunityNetworkClient.kt @@ -0,0 +1,28 @@ +package com.dscvit.vitty.network.api.community + + +import com.dscvit.vitty.util.APIConstants.Community_BASE_URL +import com.dscvit.vitty.util.APIConstants.TIMEOUT +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import java.util.concurrent.TimeUnit + +object CommunityNetworkClient { + + var retrofit: Retrofit? = null + + val retrofitClientCommunity: Retrofit + get() { + if (retrofit == null) { + val okHttpClientBuilder = OkHttpClient.Builder() + okHttpClientBuilder.connectTimeout(TIMEOUT.toLong(), TimeUnit.SECONDS) + retrofit = Retrofit.Builder() + .baseUrl(Community_BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) + .client(okHttpClientBuilder.build()) + .build() + } + return retrofit!! + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dscvit/vitty/network/api/community/RetrofitCommunityListener.kt b/app/src/main/java/com/dscvit/vitty/network/api/community/RetrofitCommunityListener.kt new file mode 100644 index 0000000..c5adfcb --- /dev/null +++ b/app/src/main/java/com/dscvit/vitty/network/api/community/RetrofitCommunityListener.kt @@ -0,0 +1,43 @@ +package com.dscvit.vitty.network.api.community + +import com.dscvit.vitty.network.api.community.responses.requests.RequestsResponse +import com.dscvit.vitty.network.api.community.responses.requests.RequestsResponseItem +import com.dscvit.vitty.network.api.community.responses.user.FriendResponse +import com.dscvit.vitty.network.api.community.responses.user.PostResponse +import com.dscvit.vitty.network.api.community.responses.user.SignInResponse +import com.dscvit.vitty.network.api.community.responses.user.UserResponse +import com.google.firebase.firestore.auth.User +import retrofit2.Call + + + + +interface RetrofitCommunitySignInListener{ + fun onSuccess(call: Call?, response: SignInResponse?) + fun onError(call: Call?, t: Throwable?) +} +interface RetrofitSelfUserListener{ + fun onSuccess(call: Call?, response: UserResponse?) + fun onError(call: Call?, t: Throwable?) +} + +interface RetrofitFriendListListener{ + fun onSuccess(call: Call?, response: FriendResponse?) + fun onError(call: Call?, t: Throwable?) +} + +interface RetrofitSearchResultListener{ + fun onSuccess(call: Call>?, response: List?) + fun onError(call: Call>?, t: Throwable?) +} + +interface RetrofitFriendRequestListener{ + fun onSuccess(call: Call?, response: RequestsResponse?) + fun onError(call: Call?, t: Throwable?) +} + +interface RetrofitUserActionListener{ + + fun onSuccess(call: Call?, response: PostResponse?) + fun onError(call: Call?, t: Throwable?) +} \ No newline at end of file diff --git a/app/src/main/java/com/dscvit/vitty/network/api/community/requests/AuthRequestBody.kt b/app/src/main/java/com/dscvit/vitty/network/api/community/requests/AuthRequestBody.kt new file mode 100644 index 0000000..97fe184 --- /dev/null +++ b/app/src/main/java/com/dscvit/vitty/network/api/community/requests/AuthRequestBody.kt @@ -0,0 +1,7 @@ +package com.dscvit.vitty.network.api.community.requests + +data class AuthRequestBody( + val reg_no: String, + val username: String, + val uuid: String +) \ No newline at end of file diff --git a/app/src/main/java/com/dscvit/vitty/network/api/community/requests/UsernameRequestBody.kt b/app/src/main/java/com/dscvit/vitty/network/api/community/requests/UsernameRequestBody.kt new file mode 100644 index 0000000..87d6d40 --- /dev/null +++ b/app/src/main/java/com/dscvit/vitty/network/api/community/requests/UsernameRequestBody.kt @@ -0,0 +1,5 @@ +package com.dscvit.vitty.network.api.community.requests + +data class UsernameRequestBody( + val username: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/dscvit/vitty/network/api/community/responses/user/PostResponse.kt b/app/src/main/java/com/dscvit/vitty/network/api/community/responses/user/PostResponse.kt new file mode 100644 index 0000000..68bd5aa --- /dev/null +++ b/app/src/main/java/com/dscvit/vitty/network/api/community/responses/user/PostResponse.kt @@ -0,0 +1,5 @@ +package com.dscvit.vitty.network.api.community.responses.user + +data class PostResponse( + val detail: String +) \ No newline at end of file diff --git a/app/src/main/java/com/dscvit/vitty/network/api/community/responses/user/SignInResponse.kt b/app/src/main/java/com/dscvit/vitty/network/api/community/responses/user/SignInResponse.kt new file mode 100644 index 0000000..193d5ab --- /dev/null +++ b/app/src/main/java/com/dscvit/vitty/network/api/community/responses/user/SignInResponse.kt @@ -0,0 +1,9 @@ +package com.dscvit.vitty.network.api.community.responses.user + +data class SignInResponse( + val name: String, + val picture: String, + val role: String, + val token: String, + val username: String +) \ No newline at end of file diff --git a/app/src/main/java/com/dscvit/vitty/network/api/community/responses/user/UserResponse.kt b/app/src/main/java/com/dscvit/vitty/network/api/community/responses/user/UserResponse.kt new file mode 100644 index 0000000..9227983 --- /dev/null +++ b/app/src/main/java/com/dscvit/vitty/network/api/community/responses/user/UserResponse.kt @@ -0,0 +1,14 @@ +package com.dscvit.vitty.network.api.community.responses.user + +import com.dscvit.vitty.network.api.community.responses.timetable.TimetableResponse + +data class UserResponse( + var email: String, + var friend_status: String, + var friends_count: Int, + var mutual_friends_count: Int, + var name: String, + var picture: String, + var timetable: TimetableResponse?, + var username: String +) diff --git a/app/src/main/java/com/dscvit/vitty/api/NetworkClient.kt b/app/src/main/java/com/dscvit/vitty/network/api/events/NetworkClient.kt similarity index 95% rename from app/src/main/java/com/dscvit/vitty/api/NetworkClient.kt rename to app/src/main/java/com/dscvit/vitty/network/api/events/NetworkClient.kt index 89f6a22..622f0ff 100644 --- a/app/src/main/java/com/dscvit/vitty/api/NetworkClient.kt +++ b/app/src/main/java/com/dscvit/vitty/network/api/events/NetworkClient.kt @@ -1,4 +1,4 @@ -package com.dscvit.vitty.api +package com.dscvit.vitty.network.api.events import com.dscvit.vitty.util.APIConstants.BASE_URL import com.dscvit.vitty.util.APIConstants.TIMEOUT diff --git a/app/src/main/java/com/dscvit/vitty/ui/auth/AuthViewModel.kt b/app/src/main/java/com/dscvit/vitty/ui/auth/AuthViewModel.kt new file mode 100644 index 0000000..219cc51 --- /dev/null +++ b/app/src/main/java/com/dscvit/vitty/ui/auth/AuthViewModel.kt @@ -0,0 +1,70 @@ +package com.dscvit.vitty.ui.auth + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.dscvit.vitty.network.api.community.APICommunityRestClient +import com.dscvit.vitty.network.api.community.RetrofitCommunitySignInListener +import com.dscvit.vitty.network.api.community.RetrofitSelfUserListener +import com.dscvit.vitty.network.api.community.RetrofitUserActionListener +import com.dscvit.vitty.network.api.community.responses.user.PostResponse +import com.dscvit.vitty.network.api.community.responses.user.SignInResponse +import com.dscvit.vitty.network.api.community.responses.user.UserResponse +import retrofit2.Call +import timber.log.Timber + +class AuthViewModel : ViewModel() { + + + private val _usernameValidity = MutableLiveData() + private val _signInResponse = MutableLiveData() + private val _user = MutableLiveData() + + val usernameValidity: MutableLiveData = _usernameValidity + val signInResponse: MutableLiveData = _signInResponse + val user: MutableLiveData = _user + + //user response has timetable as well + + fun signInAndGetTimeTable(username: String, regno: String, uuid: String) { + APICommunityRestClient.instance.signInWithUsernameRegNo(username, regno, uuid, + object : RetrofitCommunitySignInListener { + + override fun onSuccess(call: Call?, response: SignInResponse?) { + Timber.d("username: $username regno: $regno uuid: $uuid") + Timber.d("Response: ${response}") + _signInResponse.postValue(response) + + } + + override fun onError(call: Call?, t: Throwable?) { + Timber.d("Error: ${t?.message}") + _signInResponse.postValue(null) + + } + }, + object : RetrofitSelfUserListener { + override fun onSuccess(call: Call?, response: UserResponse?) { + _user.postValue(response) + } + + override fun onError(call: Call?, t: Throwable?) { + _user.postValue(null) + } + }) + } + + fun checkUsername(username: String) { + APICommunityRestClient.instance.checkUsername(username, + object : RetrofitUserActionListener { + override fun onSuccess(call: Call?, response: PostResponse?) { + _usernameValidity.postValue(response) + Timber.d("Response: ${response}") + } + + override fun onError(call: Call?, t: Throwable?) { + Timber.d("Error: ${t?.message}") + } + }) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/dscvit/vitty/ui/events/VITEventsViewModel.kt b/app/src/main/java/com/dscvit/vitty/ui/events/VITEventsViewModel.kt index 83f0ef5..325ba1b 100644 --- a/app/src/main/java/com/dscvit/vitty/ui/events/VITEventsViewModel.kt +++ b/app/src/main/java/com/dscvit/vitty/ui/events/VITEventsViewModel.kt @@ -3,8 +3,8 @@ package com.dscvit.vitty.ui.events import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import com.dscvit.vitty.api.ApiEventRestClient -import com.dscvit.vitty.api.RetrofitEventListener +import com.dscvit.vitty.network.api.events.ApiEventRestClient +import com.dscvit.vitty.network.api.events.RetrofitEventListener import com.dscvit.vitty.model.EventDetails import retrofit2.Call diff --git a/app/src/main/res/drawable/et_style.xml b/app/src/main/res/drawable/et_style.xml new file mode 100644 index 0000000..78990e4 --- /dev/null +++ b/app/src/main/res/drawable/et_style.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_info.xml b/app/src/main/res/layout/activity_add_info.xml new file mode 100644 index 0000000..d3b01a1 --- /dev/null +++ b/app/src/main/res/layout/activity_add_info.xml @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +