├── .idea
├── .name
├── .gitignore
├── compiler.xml
├── kotlinc.xml
├── vcs.xml
├── migrations.xml
├── misc.xml
├── deploymentTargetSelector.xml
├── gradle.xml
└── appInsightsSettings.xml
├── app
├── .gitignore
├── google-services.json
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── drawable
│ │ │ │ ├── heart.png
│ │ │ │ ├── logo.jpeg
│ │ │ │ ├── splash.png
│ │ │ │ ├── ellipse1.png
│ │ │ │ ├── ellipse2.png
│ │ │ │ ├── settings.png
│ │ │ │ ├── shareimg.png
│ │ │ │ ├── collaborate.png
│ │ │ │ ├── button_background_outline.xml
│ │ │ │ ├── rectangular_edit_text.xml
│ │ │ │ ├── broken_image.xml
│ │ │ │ ├── rounded_edittext.xml
│ │ │ │ ├── ic_dark_mood.xml
│ │ │ │ ├── ic_search.xml
│ │ │ │ ├── ic_calender.xml
│ │ │ │ ├── ic_share.xml
│ │ │ │ ├── background_night.xml
│ │ │ │ ├── brush_background.xml
│ │ │ │ ├── background_light.xml
│ │ │ │ ├── ic_light_mood.xml
│ │ │ │ ├── ic_launcher_foreground.xml
│ │ │ │ └── ic_launcher_background.xml
│ │ │ ├── anim
│ │ │ │ ├── fade_in.xml
│ │ │ │ ├── slide_in_left.xml
│ │ │ │ ├── enter_from_right.xml
│ │ │ │ ├── exit_to_left.xml
│ │ │ │ ├── top_animation.xml
│ │ │ │ ├── bottom_animation.xml
│ │ │ │ ├── exit_animation.xml
│ │ │ │ └── img_enter_animation.xml
│ │ │ ├── values
│ │ │ │ ├── preloaded_fonts.xml
│ │ │ │ ├── styles.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── themes.xml
│ │ │ │ ├── font_certs.xml
│ │ │ │ └── strings.xml
│ │ │ ├── font
│ │ │ │ ├── abeezee.xml
│ │ │ │ ├── poppins.xml
│ │ │ │ ├── poppins_bold.xml
│ │ │ │ └── poppins_thin.xml
│ │ │ ├── xml
│ │ │ │ ├── backup_rules.xml
│ │ │ │ └── data_extraction_rules.xml
│ │ │ ├── values-night
│ │ │ │ └── themes.xml
│ │ │ ├── layout
│ │ │ │ ├── slide3.xml
│ │ │ │ ├── fragment_no_news.xml
│ │ │ │ ├── slide2.xml
│ │ │ │ ├── slide1.xml
│ │ │ │ ├── activity_main.xml
│ │ │ │ ├── activity_splash.xml
│ │ │ │ ├── activity_sign_in.xml
│ │ │ │ ├── artical_list_item.xml
│ │ │ │ ├── activity_slider.xml
│ │ │ │ ├── activity_signup.xml
│ │ │ │ ├── activity_settings.xml
│ │ │ │ ├── activity_setting_api.xml
│ │ │ │ └── fragment_search.xml
│ │ │ └── values-ar
│ │ │ │ └── strings.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── whatnow
│ │ │ │ ├── core
│ │ │ │ ├── data
│ │ │ │ │ ├── SortBy.kt
│ │ │ │ │ ├── Categories.kt
│ │ │ │ │ ├── Languages.kt
│ │ │ │ │ └── Countries.kt
│ │ │ │ ├── api
│ │ │ │ │ ├── RetrofitFactory.kt
│ │ │ │ │ ├── DefaultRetrofitFactory.kt
│ │ │ │ │ └── APIBuilder.kt
│ │ │ │ └── ui
│ │ │ │ │ ├── SplashActivity.kt
│ │ │ │ │ ├── onboarding.kt
│ │ │ │ │ └── MainActivity.kt
│ │ │ │ ├── news
│ │ │ │ ├── data
│ │ │ │ │ ├── NewsCallable.kt
│ │ │ │ │ ├── News.kt
│ │ │ │ │ ├── NewsRepository.kt
│ │ │ │ │ └── NewsManager.kt
│ │ │ │ └── ui
│ │ │ │ │ ├── NoNewsFragment.kt
│ │ │ │ │ ├── NewsAdapter.kt
│ │ │ │ │ └── SearchFragment.kt
│ │ │ │ ├── auth
│ │ │ │ ├── AuthViewModelFactory.kt
│ │ │ │ ├── UserRepository.kt
│ │ │ │ ├── AuthViewModel.kt
│ │ │ │ ├── SignupActivity.kt
│ │ │ │ └── SignInActivity.kt
│ │ │ │ └── setteings
│ │ │ │ ├── data
│ │ │ │ └── SettingAPI.kt
│ │ │ │ └── ui
│ │ │ │ └── SettingsActivity.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── whatnow
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── example
│ │ └── whatnow
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle.kts
├── screenshots
├── login.png
├── settingAPI.png
└── searchforNews.png
├── gradle
├── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
└── libs.versions.toml
├── .gitignore
├── settings.gradle.kts
├── gradle.properties
├── gradlew.bat
├── README.md
└── gradlew
/.idea/.name:
--------------------------------------------------------------------------------
1 | What Now!
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | google-services.json
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/app/google-services.json:
--------------------------------------------------------------------------------
1 | replace this file with your own file from firebase.google.com
2 |
--------------------------------------------------------------------------------
/screenshots/login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xSA7/What-Now-NewsApp/HEAD/screenshots/login.png
--------------------------------------------------------------------------------
/screenshots/settingAPI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xSA7/What-Now-NewsApp/HEAD/screenshots/settingAPI.png
--------------------------------------------------------------------------------
/screenshots/searchforNews.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xSA7/What-Now-NewsApp/HEAD/screenshots/searchforNews.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/heart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xSA7/What-Now-NewsApp/HEAD/app/src/main/res/drawable/heart.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/logo.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xSA7/What-Now-NewsApp/HEAD/app/src/main/res/drawable/logo.jpeg
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xSA7/What-Now-NewsApp/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/drawable/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xSA7/What-Now-NewsApp/HEAD/app/src/main/res/drawable/splash.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ellipse1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xSA7/What-Now-NewsApp/HEAD/app/src/main/res/drawable/ellipse1.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ellipse2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xSA7/What-Now-NewsApp/HEAD/app/src/main/res/drawable/ellipse2.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xSA7/What-Now-NewsApp/HEAD/app/src/main/res/drawable/settings.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/shareimg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xSA7/What-Now-NewsApp/HEAD/app/src/main/res/drawable/shareimg.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/collaborate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xSA7/What-Now-NewsApp/HEAD/app/src/main/res/drawable/collaborate.png
--------------------------------------------------------------------------------
/app/src/main/res/anim/fade_in.xml:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/whatnow/core/data/SortBy.kt:
--------------------------------------------------------------------------------
1 | package com.example.whatnow.core.data
2 |
3 | enum class SortBy(val value: String) {
4 | RELEVANCY("relevancy"),
5 | POPULARITY("popularity"),
6 | PUBLISHED_AT("publishedAt"),
7 | None("");
8 | }
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_in_left.xml:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/whatnow/core/api/RetrofitFactory.kt:
--------------------------------------------------------------------------------
1 | // File: app/src/main/java/com/example/whatnow/api/RetrofitFactory.kt
2 | package com.example.whatnow.core.api
3 |
4 | import retrofit2.Retrofit
5 |
6 | interface RetrofitFactory {
7 | fun createRetrofit(): Retrofit
8 | }
--------------------------------------------------------------------------------
/app/src/main/res/anim/enter_from_right.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/exit_to_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Aug 28 22:50:28 EEST 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/whatnow/news/data/NewsCallable.kt:
--------------------------------------------------------------------------------
1 | package com.example.whatnow.news.data
2 |
3 | import retrofit2.Call
4 | import retrofit2.http.GET
5 | import retrofit2.http.Url
6 |
7 | interface NewsCallable {
8 | @GET
9 | fun getNews(@Url url: String): Call
10 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .idea
15 | .cxx
16 | local.properties
17 |
--------------------------------------------------------------------------------
/.idea/migrations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/whatnow/news/data/News.kt:
--------------------------------------------------------------------------------
1 | package com.example.whatnow.news.data
2 |
3 |
4 | data class News(
5 | val articles: ArrayList,
6 | val totalResults: Int = articles.size
7 | )
8 |
9 | data class Articles(
10 | val title: String,
11 | val url: String,
12 | val urlToImage: String
13 |
14 | )
15 |
--------------------------------------------------------------------------------
/app/src/main/res/values/preloaded_fonts.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - @font/abeezee
5 | - @font/poppins
6 | - @font/poppins_bold
7 | - @font/poppins_thin
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_background_outline.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/font/abeezee.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/font/poppins.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/top_animation.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/font/poppins_bold.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/font/poppins_thin.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/bottom_animation.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
13 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/test/java/com/example/whatnow/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.whatnow
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/res/anim/exit_animation.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/img_enter_animation.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/rectangular_edit_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/broken_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/rounded_edittext.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/whatnow/news/data/NewsRepository.kt:
--------------------------------------------------------------------------------
1 | package com.example.whatnow.news.data
2 |
3 | import com.example.whatnow.core.api.RetrofitFactory
4 |
5 | class NewsRepository(private val retrofitFactory: RetrofitFactory) {
6 |
7 | private val newsCallable: NewsCallable
8 |
9 | init {
10 | val retrofit = retrofitFactory.createRetrofit()
11 | newsCallable = retrofit.create(NewsCallable::class.java)
12 | }
13 |
14 | fun getNews(queryUrl: String) = newsCallable.getNews(queryUrl)
15 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_dark_mood.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_search.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FF000000
4 | #808080
5 | #FFFFFFFF
6 | #87CEEB
7 | #ADD8E6
8 |
9 |
10 | //onboarding colors
11 | #F0F4F3
12 | #50C2C9
13 |
14 | //indicator color
15 | #C4C4C4
16 | #50C2C9
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_calender.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/whatnow/core/api/DefaultRetrofitFactory.kt:
--------------------------------------------------------------------------------
1 | // File: app/src/main/java/com/example/whatnow/api/DefaultRetrofitFactory.kt
2 | package com.example.whatnow.core.api
3 |
4 | import com.example.whatnow.BuildConfig
5 | import retrofit2.Retrofit
6 | import retrofit2.converter.gson.GsonConverterFactory
7 |
8 | class DefaultRetrofitFactory : RetrofitFactory {
9 | override fun createRetrofit(): Retrofit {
10 | return Retrofit.Builder()
11 | .baseUrl(BuildConfig.API_baseUrl)
12 | .addConverterFactory(GsonConverterFactory.create())
13 | .build()
14 | }
15 | }
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google {
4 | content {
5 | includeGroupByRegex("com\\.android.*")
6 | includeGroupByRegex("com\\.google.*")
7 | includeGroupByRegex("androidx.*")
8 | }
9 | }
10 | mavenCentral()
11 | gradlePluginPortal()
12 | }
13 | }
14 | dependencyResolutionManagement {
15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
16 | repositories {
17 | google()
18 | mavenCentral()
19 | }
20 | }
21 |
22 | rootProject.name = "What Now!"
23 | include(":app")
24 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/whatnow/auth/AuthViewModelFactory.kt:
--------------------------------------------------------------------------------
1 | package com.example.whatnow.auth
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.ViewModelProvider
5 | import com.example.whatnow.auth.UserRepository
6 |
7 | // Factory class for creating AuthViewModel instances
8 | class AuthViewModelFactory(private val repository: UserRepository) : ViewModelProvider.Factory {
9 | @Suppress("UNCHECKED_CAST")
10 | override fun create(modelClass: Class): T {
11 | if (modelClass.isAssignableFrom(AuthViewModel::class.java)) {
12 | return AuthViewModel(repository) as T
13 | }
14 | throw IllegalArgumentException("Unknown ViewModel class")
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.idea/deploymentTargetSelector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/whatnow/core/data/Categories.kt:
--------------------------------------------------------------------------------
1 | package com.example.whatnow.core.data
2 |
3 | enum class Categories(val code: String) {
4 | BUSINESS("business"),
5 | ENTERTAINMENT("entertainment"),
6 | GENERAL("general"),
7 | HEALTH("health"),
8 | SCIENCE("science"),
9 | SPORTS("sports"),
10 | TECHNOLOGY("technology"),
11 | None("");
12 | companion object{
13 | fun returnAsEnum(toBeReturnedAsEnum: String): Categories {
14 | for (category in entries) {
15 | if (category.toString() == toBeReturnedAsEnum) {
16 | return category
17 |
18 | }
19 | }
20 | return None
21 | }
22 | }
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_share.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/example/whatnow/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.whatnow
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.example.whatnow", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/java/com/example/whatnow/core/data/Languages.kt:
--------------------------------------------------------------------------------
1 | package com.example.whatnow.core.data
2 |
3 |
4 | enum class Languages(val code: String) {
5 | Arabic("ar"),
6 | German("de"),
7 | English("en"),
8 | Spanish("es"),
9 | French("fr"),
10 | Italian("it"),
11 | Dutch("nl"),
12 | Norwegian("no"),
13 | Portuguese("pt"),
14 | Russian("ru"),
15 | Swedish("sv"),
16 | Urdu("ud"),
17 | Chinese("zh"),
18 | None("");
19 |
20 | companion object {
21 | fun returnAsEnum(toBeReturnedAsEnum: String): Languages {
22 | for (Language in Languages.entries) {
23 | if (Language.toString() == toBeReturnedAsEnum) {
24 | return Language
25 |
26 | }
27 | }
28 | return None
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/background_night.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
10 |
11 |
12 | -
13 |
14 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/brush_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
10 |
11 |
12 | -
13 |
14 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/background_light.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
10 |
11 |
12 | -
13 |
14 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/whatnow/core/data/Countries.kt:
--------------------------------------------------------------------------------
1 | package com.example.whatnow.core.data
2 |
3 | enum class Countries(val code: String) {
4 | ARGENTINA("ar"),
5 | AUSTRALIA("au"),
6 | AUSTRIA("at"),
7 | BELGIUM("be"),
8 | BRAZIL("br"),
9 | CANADA("ca"),
10 | CHINA("cn"),
11 | EGYPT("eg"),
12 | GERMANY("de"),
13 | HUNGARY("hu"),
14 | ITALY("it"),
15 | JAPAN("jp"),
16 | MALAYSIA("my"),
17 | MOROCCO("ma"),
18 | NORWAY("no"),
19 | POLAND("pl"),
20 | RUSSIA("ru"),
21 | SAUDI_ARABIA("sa"),
22 | SINGAPORE("sg"),
23 | SWITZERLAND("ch"),
24 | TURKEY("tr"),
25 | UAE("ae"),
26 | UNITED_KINGDOM("gb"),
27 | US("us"),
28 | None("");
29 | companion object{
30 | fun returnAsEnum(toBeReturnedAsEnum: String): Countries {
31 | for (country in entries) {
32 | if (country.toString() == toBeReturnedAsEnum) {
33 | return country
34 |
35 | }
36 | }
37 | return None
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 | - @color/black
17 |
18 |
19 |
23 |
24 |
25 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
17 |
18 | - @color/white
19 |
20 |
24 |
25 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_light_mood.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/appInsightsSettings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. For more details, visit
12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/app/src/main/java/com/example/whatnow/news/ui/NoNewsFragment.kt:
--------------------------------------------------------------------------------
1 | package com.example.whatnow.news.ui
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import androidx.fragment.app.Fragment
9 | import com.example.whatnow.core.ui.MainActivity
10 | import com.example.whatnow.databinding.FragmentNoNewsBinding
11 |
12 | class NoNewsFragment : Fragment() {
13 |
14 | private var _binding: FragmentNoNewsBinding? = null
15 | private val binding get() = _binding!!
16 |
17 | override fun onCreateView(
18 | inflater: LayoutInflater, container: ViewGroup?,
19 | savedInstanceState: Bundle?
20 | ): View? {
21 | // Inflate the layout using view binding
22 | _binding = FragmentNoNewsBinding.inflate(inflater, container, false)
23 |
24 | setupUI()
25 |
26 | return binding.root
27 | }
28 |
29 | private fun setupUI() {
30 | binding.tryAgainBtn.setOnClickListener {
31 | requireActivity().supportFragmentManager.popBackStack()
32 | val intent = Intent(requireContext(), MainActivity::class.java)
33 | intent.putExtra("openSearchFragment", true)
34 | startActivity(intent)
35 | requireActivity().finish()
36 | }
37 | }
38 |
39 | override fun onDestroyView() {
40 | super.onDestroyView()
41 | _binding = null
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/slide3.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
26 |
27 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_no_news.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
18 |
19 |
27 |
28 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/slide2.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
27 |
28 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/slide1.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
27 |
28 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
9 |
10 |
16 |
19 |
22 |
23 |
24 |
25 |
31 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
18 |
19 |
25 |
26 |
27 |
34 |
35 |
40 |
41 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/res/values-ar/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ما الذي يثير اهتمامك؟
4 | • فئات
5 | • دولة
6 | • المواضيع
7 | أدخل موضوعا
8 | تحقق
9 | الإعدادات
10 | الوضع المظلم
11 | اللغه
12 | البريد الإلكتروني
13 | كلمة المرور
14 | نسيت كلمة المرور؟
15 | تسجيل الدخول
16 | ليس مستخدمًا؟
17 | تأكيد كلمة المرور
18 | التسجيل
19 | هل أنت مستخدم بالفعل؟
20 | لم يتم العثور على أي أخبار.
21 | حاول البحث مرة أخرى
22 | ما الذي تبحث عنه؟
23 | خيارات البحث المتقدمة:
24 | الترتيب حسب:
25 | الفرز حسب التاريخ
26 | ذات الصلة
27 | الشعبية
28 | من:
29 | الي:
30 | البحث!
31 |
32 |
33 | أخبار يوم ييوم
34 | إشعارات فورية
35 |
36 | زود معلوماتك بالأخبارنا
37 | What Now
38 | أخبار في أي وقت
39 | news_Image
40 | ...
41 | shareic
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/whatnow/auth/UserRepository.kt:
--------------------------------------------------------------------------------
1 | package com.example.whatnow.auth
2 |
3 | import com.google.firebase.auth.FirebaseAuth
4 | import com.google.firebase.auth.ktx.auth
5 | import com.google.firebase.ktx.Firebase
6 |
7 | class UserRepository {
8 | private val auth: FirebaseAuth = Firebase.auth
9 |
10 | fun login(email: String, password: String, callback: (Result) -> Unit) {
11 | auth.signInWithEmailAndPassword(email, password)
12 | .addOnCompleteListener { task ->
13 | if (task.isSuccessful) {
14 | callback(Result.success(auth.currentUser!!.isEmailVerified))
15 | } else {
16 | callback(Result.failure(task.exception ?: Exception("Login failed")))
17 | }
18 | }
19 | }
20 |
21 | fun signUp(email: String, password: String, callback: (Result) -> Unit) {
22 | auth.createUserWithEmailAndPassword(email, password)
23 | .addOnCompleteListener { task ->
24 | if (task.isSuccessful) {
25 | sendVerificationEmail { callback(it) }
26 | } else {
27 | callback(Result.failure(task.exception ?: Exception("Sign up failed")))
28 | }
29 | }
30 | }
31 |
32 | fun sendPasswordResetEmail(email: String, callback: (Result) -> Unit) {
33 | auth.sendPasswordResetEmail(email)
34 | .addOnCompleteListener { task ->
35 | if (task.isSuccessful) {
36 | callback(Result.success(true))
37 | } else {
38 | callback(Result.failure(task.exception ?: Exception("Failed to send reset email")))
39 | }
40 | }
41 | }
42 |
43 | private fun sendVerificationEmail(callback: (Result) -> Unit) {
44 | auth.currentUser?.sendEmailVerification()
45 | ?.addOnCompleteListener { task ->
46 | if (task.isSuccessful) {
47 | callback(Result.success(true))
48 | } else {
49 | callback(Result.failure(task.exception ?: Exception("Failed to send verification email")))
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/whatnow/core/ui/SplashActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.whatnow.core.ui
2 |
3 | import android.content.Intent
4 | import android.content.SharedPreferences
5 | import android.os.Bundle
6 | import android.os.Handler
7 | import android.os.Looper
8 | import android.util.Log
9 | import android.view.View
10 | import android.view.animation.AnimationUtils
11 | import android.widget.ImageView
12 | import androidx.appcompat.app.AppCompatActivity
13 | import com.example.whatnow.R
14 | import com.example.whatnow.auth.SignInActivity
15 |
16 | class SplashActivity : AppCompatActivity() {
17 |
18 | private val SPLASH_DISPLAY_LENGTH: Long = 3000
19 |
20 | override fun onCreate(savedInstanceState: Bundle?) {
21 |
22 | super.onCreate(savedInstanceState)
23 | setContentView(R.layout.activity_splash)
24 | val enterAnimation = AnimationUtils.loadAnimation(this, R.anim.img_enter_animation)
25 | val exitAnimation = AnimationUtils.loadAnimation(this, R.anim.exit_animation)
26 | val rootLayout = findViewById(R.id.splash)
27 |
28 | val imageView = findViewById(R.id.splashImage)
29 | imageView.startAnimation(enterAnimation)
30 |
31 | Handler(Looper.getMainLooper()).postDelayed({
32 | val sharedPreferences: SharedPreferences =
33 | getSharedPreferences("onboarding_prefs", MODE_PRIVATE)
34 | val isFirstTime: Boolean = sharedPreferences.getBoolean("isFirstTime", true)
35 |
36 | Log.d("SplashActivity", "isFirstTime: $isFirstTime")
37 |
38 | val intent = if (isFirstTime) {
39 | Log.d("SplashActivity", "Redirecting to Onboarding")
40 | Intent(this, Onboarding::class.java)
41 | } else {
42 | Log.d("SplashActivity", "Redirecting to SignupActivity")
43 | Intent(this, SignInActivity::class.java)
44 | }
45 |
46 | Log.d("SplashActivity", "Starting intent: ${intent.component?.className}")
47 |
48 |
49 |
50 | Handler(Looper.getMainLooper()).postDelayed({
51 | startActivity(intent)
52 | overridePendingTransition(R.anim.img_enter_animation, R.anim.exit_animation)
53 | finish()
54 | }, exitAnimation.duration)
55 |
56 | }, SPLASH_DISPLAY_LENGTH)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/whatnow/news/ui/NewsAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.example.whatnow.news.ui
2 |
3 |
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.util.Log
7 | import android.view.LayoutInflater
8 | import android.view.ViewGroup
9 | import androidx.core.app.ShareCompat
10 | import androidx.core.net.toUri
11 | import androidx.recyclerview.widget.RecyclerView.ViewHolder
12 | import androidx.recyclerview.widget.RecyclerView.Adapter
13 | import com.bumptech.glide.Glide
14 | import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
15 | import com.example.whatnow.R
16 | import com.example.whatnow.databinding.ArticalListItemBinding
17 | import com.example.whatnow.news.data.Articles
18 |
19 | class NewsAdapter(val activity: Context, val newsList: ArrayList) :
20 | Adapter() {
21 | class NewsViewHolder(val binding: ArticalListItemBinding) : ViewHolder(binding.root)
22 |
23 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewsViewHolder {
24 | val binding =
25 | ArticalListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
26 | return NewsViewHolder(binding)
27 | }
28 |
29 | //Single-expression function
30 | override fun getItemCount() = newsList.size
31 |
32 | override fun onBindViewHolder(holder: NewsViewHolder, position: Int) {
33 | Log.d("trace", "Article image: ${newsList[position].urlToImage}")
34 | holder.binding.articalTitle.text = newsList[position].title
35 | val imageUrl = newsList[position].urlToImage
36 | Glide
37 | .with(holder.binding.articalImage.context)
38 | .load(imageUrl)
39 | .error(R.drawable.broken_image)
40 | .transition(DrawableTransitionOptions.withCrossFade(1000))
41 | .into(holder.binding.articalImage)
42 |
43 | val url = newsList[position].url
44 | holder.binding.articalContiner.setOnClickListener {
45 | val intent = Intent(Intent.ACTION_VIEW, url.toUri())
46 | activity.startActivity(intent)
47 | }
48 | holder.binding.shareBtn.setOnClickListener {
49 | ShareCompat
50 | .IntentBuilder(activity)
51 | .setType("text/plain")
52 | .setChooserTitle("Share Article")
53 | .setText(url)
54 | .startChooser()
55 | }
56 | }
57 |
58 |
59 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_splash.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
23 |
24 |
37 |
38 |
53 |
54 |
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application)
3 | alias(libs.plugins.jetbrains.kotlin.android)
4 | id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
5 | id("com.google.gms.google-services")
6 |
7 | }
8 |
9 | android {
10 | namespace = "com.example.whatnow"
11 | compileSdk = 34
12 | buildFeatures { viewBinding = true
13 | //noinspection DataBindingWithoutKapt
14 | dataBinding = true
15 | }
16 |
17 | defaultConfig {
18 | applicationId = "com.example.whatnow"
19 | minSdk = 26
20 | targetSdk = 34
21 | versionCode = 1
22 | versionName = "2.0"
23 | android.buildFeatures.buildConfig = true
24 |
25 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
26 | }
27 |
28 | buildTypes {
29 | release {
30 | isMinifyEnabled = false
31 | proguardFiles(
32 | getDefaultProguardFile("proguard-android-optimize.txt"),
33 | "proguard-rules.pro"
34 | )
35 | }
36 | }
37 | compileOptions {
38 | sourceCompatibility = JavaVersion.VERSION_1_8
39 | targetCompatibility = JavaVersion.VERSION_1_8
40 | }
41 | kotlinOptions {
42 | jvmTarget = "1.8"
43 | }
44 | }
45 |
46 | dependencies {
47 |
48 | implementation(libs.androidx.core.ktx)
49 | implementation(libs.androidx.appcompat)
50 | implementation(libs.material)
51 | implementation(libs.androidx.activity)
52 | implementation(libs.androidx.constraintlayout)
53 | implementation(libs.firebase.auth.ktx)
54 | implementation(libs.firebase.common.ktx)
55 | testImplementation(libs.junit)
56 | androidTestImplementation(libs.androidx.junit)
57 | androidTestImplementation(libs.androidx.espresso.core)
58 | implementation(libs.retrofit)
59 | implementation (libs.converter.gson)
60 | implementation (libs.glide)
61 | implementation(libs.androidx.swiperefreshlayout)
62 |
63 | implementation(platform("com.google.firebase:firebase-bom:33.2.0"))
64 | implementation ("com.google.android.material:material:1.9.0")
65 | implementation("com.google.firebase:firebase-auth")
66 |
67 | implementation ("com.airbnb.android:lottie:3.4.0")
68 | implementation("androidx.navigation:navigation-fragment-ktx:2.7.7")
69 | implementation("androidx.navigation:navigation-ui-ktx:2.7.7")
70 |
71 |
72 | implementation( "com.ramotion.foldingcell:folding-cell:1.2.3")
73 | }
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp = "8.6.1"
3 | converterGson = "2.11.0"
4 | firebaseBom = "33.2.0"
5 | glide = "4.16.0"
6 | kotlin = "1.9.0"
7 | coreKtx = "1.13.1"
8 | junit = "4.13.2"
9 | junitVersion = "1.2.1"
10 | espressoCore = "3.6.1"
11 | appcompat = "1.7.0"
12 | material = "1.12.0"
13 | activity = "1.9.2"
14 | constraintlayout = "2.1.4"
15 | materialVersion = "1.9.0"
16 | retrofit = "2.11.0"
17 | secretsGradlePlugin = "2.0.1"
18 | swiperefreshlayout = "1.1.0"
19 | firebaseAuthKtx = "23.0.0"
20 | firebaseCommonKtx = "21.0.0"
21 |
22 | [libraries]
23 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
24 | androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "swiperefreshlayout" }
25 | converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "converterGson" }
26 | firebase-auth = { module = "com.google.firebase:firebase-auth" }
27 | firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" }
28 | glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
29 | google-firebase-auth = { module = "com.google.firebase:firebase-auth" }
30 | junit = { group = "junit", name = "junit", version.ref = "junit" }
31 | androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
32 | androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
33 | androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
34 | #noinspection SimilarGradleDependency
35 | material = { group = "com.google.android.material", name = "material", version.ref = "material" }
36 | androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
37 | androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
38 | #noinspection SimilarGradleDependency
39 | material-v190 = { module = "com.google.android.material:material", version.ref = "materialVersion" }
40 | retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
41 | secrets-gradle-plugin = { module = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin", version.ref = "secretsGradlePlugin" }
42 | firebase-auth-ktx = { group = "com.google.firebase", name = "firebase-auth-ktx", version.ref = "firebaseAuthKtx" }
43 | firebase-common-ktx = { group = "com.google.firebase", name = "firebase-common-ktx", version.ref = "firebaseCommonKtx" }
44 |
45 | [plugins]
46 | android-application = { id = "com.android.application", version.ref = "agp" }
47 | jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/whatnow/auth/AuthViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.example.whatnow.auth
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.ViewModel
6 | import com.google.firebase.auth.FirebaseAuth
7 |
8 | class AuthViewModel(private val repository: UserRepository) : ViewModel() {
9 |
10 | private val _signUpResult = MutableLiveData>()
11 | val signUpResult: LiveData> get() = _signUpResult
12 |
13 | private val _passwordResetResult = MutableLiveData>()
14 | val passwordResetResult: LiveData> get() = _passwordResetResult
15 |
16 | private val _signInResult = MutableLiveData>()
17 | val signInResult: LiveData> get() = _signInResult
18 |
19 | private val auth: FirebaseAuth = FirebaseAuth.getInstance()
20 |
21 | fun signUp(email: String, password: String) {
22 | auth.createUserWithEmailAndPassword(email, password)
23 | .addOnCompleteListener { task ->
24 | if (task.isSuccessful) {
25 | verifyEmail()
26 | } else {
27 | _signUpResult.value = Result.failure(task.exception ?: Exception("Sign up failed"))
28 | }
29 | }
30 | }
31 |
32 | private fun verifyEmail() {
33 | auth.currentUser?.sendEmailVerification()
34 | ?.addOnCompleteListener { task ->
35 | if (task.isSuccessful) {
36 | _signUpResult.value = Result.success(true)
37 | } else {
38 | _signUpResult.value = Result.failure(task.exception ?: Exception("Failed to send verification email"))
39 | }
40 | }
41 | }
42 |
43 | fun sendPasswordResetEmail(email: String) {
44 | auth.sendPasswordResetEmail(email)
45 | .addOnCompleteListener { task ->
46 | if (task.isSuccessful) {
47 | _passwordResetResult.value = Result.success(true)
48 | } else {
49 | _passwordResetResult.value = Result.failure(task.exception ?: Exception("Failed to send password reset email"))
50 | }
51 | }
52 | }
53 |
54 | fun signIn(email: String, password: String) {
55 | auth.signInWithEmailAndPassword(email, password)
56 | .addOnCompleteListener { task ->
57 | if (task.isSuccessful) {
58 | if (auth.currentUser!!.isEmailVerified) {
59 | _signInResult.value = Result.success(true)
60 | } else {
61 | _signInResult.value = Result.failure(Exception("Please verify your email first"))
62 | }
63 | } else {
64 | _signInResult.value = Result.failure(task.exception ?: Exception("Sign in failed"))
65 | }
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/whatnow/core/ui/onboarding.kt:
--------------------------------------------------------------------------------
1 | package com.example.whatnow.core.ui
2 |
3 | import android.content.Intent
4 | import android.content.SharedPreferences
5 | import android.os.Bundle
6 | import android.widget.Button
7 | import android.widget.TextView
8 | import android.widget.ViewFlipper
9 | import androidx.appcompat.app.AppCompatActivity
10 | import com.example.whatnow.R
11 | import com.example.whatnow.auth.SignupActivity
12 |
13 | class Onboarding : AppCompatActivity() {
14 |
15 | private lateinit var viewFlipper: ViewFlipper
16 | private lateinit var nextButton: Button
17 | private lateinit var backButton: Button
18 | private lateinit var skipButton: Button
19 | private lateinit var textSlide: TextView
20 |
21 | private val slideTexts = arrayOf(
22 | "Welcome to our app",
23 | "Discover amazing features",
24 | "Get started now"
25 | )
26 | private var currentIndex = 0
27 |
28 | override fun onCreate(savedInstanceState: Bundle?) {
29 | super.onCreate(savedInstanceState)
30 | setContentView(R.layout.activity_slider)
31 |
32 | viewFlipper = findViewById(R.id.viewFlipper)
33 | nextButton = findViewById(R.id.nextbtn)
34 | backButton = findViewById(R.id.backbtn)
35 | skipButton = findViewById(R.id.skipButton)
36 | textSlide = findViewById(R.id.textSlide)
37 |
38 | updateSlideText()
39 |
40 | viewFlipper.setOutAnimation(this, android.R.anim.slide_out_right)
41 | viewFlipper.setInAnimation(this, android.R.anim.slide_in_left)
42 |
43 | nextButton.setOnClickListener {
44 | if (currentIndex < viewFlipper.childCount - 1) {
45 | currentIndex++
46 | viewFlipper.showNext()
47 | updateSlideText()
48 | } else {
49 | navigateToSignup()
50 | }
51 | }
52 |
53 | backButton.setOnClickListener {
54 | if (currentIndex > 0) {
55 | currentIndex--
56 | viewFlipper.showPrevious()
57 | updateSlideText()
58 | }
59 | }
60 |
61 | skipButton.setOnClickListener {
62 | skipOnboarding()
63 | }
64 | }
65 |
66 | private fun updateSlideText() {
67 | textSlide.text = slideTexts[currentIndex]
68 | }
69 |
70 | private fun navigateToSignup() {
71 | val sharedPreferences: SharedPreferences =
72 | getSharedPreferences("onboarding_prefs", MODE_PRIVATE)
73 | sharedPreferences.edit().putBoolean("isFirstTime", false).apply()
74 |
75 | startActivity(Intent(this, SignupActivity::class.java))
76 | finish()
77 | }
78 |
79 | private fun skipOnboarding() {
80 | val sharedPreferences: SharedPreferences =
81 | getSharedPreferences("onboarding_prefs", MODE_PRIVATE)
82 | sharedPreferences.edit().putBoolean("isFirstTime", false).apply()
83 | navigateToSignup()
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/whatnow/news/data/NewsManager.kt:
--------------------------------------------------------------------------------
1 | package com.example.whatnow.news.data
2 |
3 | import android.content.Context
4 | import android.util.Log
5 | import androidx.core.view.isVisible
6 | import com.example.whatnow.BuildConfig
7 | import com.example.whatnow.core.ui.MainActivity
8 | import com.example.whatnow.core.api.APIBuilder
9 | import com.example.whatnow.core.data.Languages
10 | import com.example.whatnow.core.data.SortBy
11 | import com.example.whatnow.databinding.ActivityMainBinding
12 | import com.example.whatnow.news.ui.NewsAdapter
13 | import retrofit2.Call
14 | import retrofit2.Callback
15 | import retrofit2.Response
16 |
17 | class NewsManager(
18 | private val context: Context,
19 | private val binding: ActivityMainBinding,
20 | private val newsCallable: NewsCallable
21 | ) {
22 |
23 | fun performSearch(
24 | query: String,
25 | from: String = "",
26 | to: String = "",
27 | language: Languages = Languages.None,
28 | sortBy: SortBy = SortBy.None
29 | ) {
30 | val queryUrl = APIBuilder.Builder(BuildConfig.API_Topics_Everything)
31 | .setQuery(query)
32 | .setFromDate(from)
33 | .setToDate(to)
34 | .setLanguage(language)
35 | .setSortBy(sortBy)
36 | .build()
37 | .buildUrl()
38 | loadNews(queryUrl)
39 | }
40 |
41 | fun loadNews(queryUrl: String) {
42 | Log.d("Call", "API Call: $queryUrl")
43 | val news = newsCallable.getNews(queryUrl)
44 | news.enqueue(object : Callback {
45 | override fun onResponse(call: Call, response: Response) {
46 | val news = response.body()
47 | if (news != null) {
48 | if (news.totalResults == 0) {
49 | Log.d("NewsManager", "No news found")
50 | (context as MainActivity).showNoNewsFragment()
51 | } else {
52 | val adapter = news.articles
53 | adapter.removeAll{ it.title=="[Removed]" }
54 | showNews(adapter)
55 | }
56 | } else {
57 | Log.d("NewsManager", "News response is null")
58 | (context as MainActivity).showNoNewsFragment()
59 | }
60 | binding.progressBar.isVisible = false
61 | binding.swipeRefresh.isRefreshing = false
62 | }
63 |
64 | override fun onFailure(call: Call, t: Throwable) {
65 | Log.d("NewsManager", "Error: ${t.message}")
66 | binding.progressBar.isVisible = false
67 | binding.swipeRefresh.isRefreshing = false
68 | }
69 | })
70 | }
71 |
72 |
73 | private fun showNews(articles: ArrayList) {
74 | val adapter = NewsAdapter(context, articles)
75 | binding.newsList.adapter = adapter
76 | adapter.notifyDataSetChanged()
77 | }
78 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_sign_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
31 |
32 |
46 |
47 |
57 |
58 |
69 |
70 |
80 |
81 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/app/src/main/res/values/font_certs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - @array/com_google_android_gms_fonts_certs_dev
5 | - @array/com_google_android_gms_fonts_certs_prod
6 |
7 |
8 | -
9 | MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
10 |
11 |
12 |
13 | -
14 | MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/artical_list_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
14 |
15 |
26 |
27 |
34 |
35 |
46 |
47 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/whatnow/news/ui/SearchFragment.kt:
--------------------------------------------------------------------------------
1 | package com.example.whatnow.news.ui
2 |
3 | import android.app.DatePickerDialog
4 | import android.content.Context
5 | import android.os.Bundle
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import android.widget.ArrayAdapter
10 | import android.widget.ImageButton
11 | import android.widget.TextView
12 | import androidx.fragment.app.Fragment
13 | import com.example.whatnow.R
14 | import com.example.whatnow.databinding.FragmentSearchBinding
15 | import java.text.SimpleDateFormat
16 | import java.util.Calendar
17 | import java.util.Locale
18 |
19 | class SearchFragment : Fragment(R.layout.fragment_search) {
20 |
21 | private var _binding: FragmentSearchBinding? = null
22 | private val binding get() = _binding!!
23 | private var listener: OnSearchListener? = null
24 |
25 | interface OnSearchListener {
26 | fun onSearch(query: String, language: String, fromDate: String, toDate: String)
27 | }
28 |
29 | override fun onAttach(context: Context) {
30 | super.onAttach(context)
31 | if (context is OnSearchListener) {
32 | listener = context
33 | } else {
34 | throw RuntimeException("$context must implement OnSearchListener")
35 | }
36 | }
37 |
38 | override fun onCreateView(
39 | inflater: LayoutInflater, container: ViewGroup?,
40 | savedInstanceState: Bundle?
41 | ): View? {
42 | _binding = FragmentSearchBinding.inflate(inflater, container, false)
43 | return binding.root
44 | }
45 |
46 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
47 | super.onViewCreated(view, savedInstanceState)
48 |
49 | val calendar = Calendar.getInstance()
50 | setupDatePickerDialog(binding.fromDateIb, binding.selectedFromDateTv, calendar)
51 | setupDatePickerDialog(binding.toDateIb, binding.selectedToDateTv, calendar)
52 |
53 | val adapter = ArrayAdapter.createFromResource(
54 | requireContext(),
55 | R.array.languages_array,
56 | android.R.layout.simple_spinner_item
57 | )
58 | adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
59 | binding.LanguageSpinner.adapter = adapter
60 |
61 | binding.findBtn.setOnClickListener {
62 | val queryText = binding.editQuery.editText?.text.toString()
63 | val selectedLanguage = binding.LanguageSpinner.selectedItem.toString()
64 | val fromDate = binding.selectedFromDateTv.text.toString()
65 | val toDate = binding.selectedToDateTv.text.toString()
66 |
67 | listener?.onSearch(queryText, selectedLanguage, fromDate, toDate)
68 | parentFragmentManager.popBackStack()
69 | }
70 | }
71 |
72 | private fun setupDatePickerDialog(
73 | button: ImageButton,
74 | dateTextView: TextView,
75 | calendar: Calendar
76 | ) {
77 | val datePickerDialog = DatePickerDialog(
78 | requireContext(),
79 | { _, year, monthOfYear, dayOfMonth ->
80 | calendar.set(year, monthOfYear, dayOfMonth)
81 | val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
82 | dateTextView.text = dateFormat.format(calendar.time)
83 | },
84 | calendar.get(Calendar.YEAR),
85 | calendar.get(Calendar.MONTH),
86 | calendar.get(Calendar.DAY_OF_MONTH)
87 | )
88 |
89 | button.setOnClickListener {
90 | datePickerDialog.show()
91 | }
92 | }
93 |
94 | override fun onDestroyView() {
95 | super.onDestroyView()
96 | _binding = null
97 | }
98 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_slider.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
20 |
21 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
50 |
51 |
60 |
61 |
68 |
69 |
77 |
78 |
86 |
87 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_signup.xml:
--------------------------------------------------------------------------------
1 |
14 |
15 |
22 |
23 |
37 |
38 |
52 |
53 |
67 |
68 |
79 |
80 |
90 |
91 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/whatnow/setteings/data/SettingAPI.kt:
--------------------------------------------------------------------------------
1 | package com.example.whatnow.setteings.data
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import android.widget.ArrayAdapter
6 | import android.widget.Spinner
7 | import androidx.activity.enableEdgeToEdge
8 | import androidx.appcompat.app.AppCompatActivity
9 | import androidx.core.view.ViewCompat
10 | import androidx.core.view.WindowInsetsCompat
11 | import com.example.whatnow.BuildConfig
12 | import com.example.whatnow.core.ui.MainActivity
13 | import com.example.whatnow.R
14 | import com.example.whatnow.core.api.APIBuilder
15 | import com.example.whatnow.core.data.Categories
16 | import com.example.whatnow.core.data.Countries
17 | import com.example.whatnow.databinding.ActivitySettingApiBinding
18 |
19 | class SettingAPI : AppCompatActivity() {
20 |
21 | override fun onCreate(savedInstanceState: Bundle?) {
22 | super.onCreate(savedInstanceState)
23 | enableEdgeToEdge()
24 | val binding = ActivitySettingApiBinding.inflate(layoutInflater)
25 | setContentView(binding.root)
26 | ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
27 | val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
28 | v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
29 | insets
30 | }
31 | val countrySpinner: Spinner = binding.countrySpinner
32 | // Create an ArrayAdapter using the string array and a default spinner layout.
33 | ArrayAdapter.createFromResource(
34 | this,
35 | R.array.countries_array,
36 | android.R.layout.simple_spinner_item
37 | ).also { adapter ->
38 | // Specify the layout to use when the list of choices appears.
39 | adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
40 | // Apply the adapter to the spinner.
41 | countrySpinner.adapter = adapter
42 | }
43 | val categoriesSpinner: Spinner = binding.categoriesSpinner
44 | // Create an ArrayAdapter using the string array and a default spinner layout.
45 | ArrayAdapter.createFromResource(
46 | this,
47 | R.array.categories_array,
48 | android.R.layout.simple_spinner_item
49 | ).also { adapter ->
50 | // Specify the layout to use when the list of choices appears.
51 | adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
52 | // Apply the adapter to the spinner.
53 | categoriesSpinner.adapter = adapter
54 | }
55 |
56 | binding.submit.setOnClickListener {
57 | val topics = binding.topicEt.text.toString()
58 | .split(" ")
59 | .filter { it.isNotEmpty() }
60 | .joinToString("+") { it.replace(Regex("[^A-Za-z0-9]"), "") }
61 | var selectedCountry =
62 | Countries.returnAsEnum(countrySpinner.selectedItem.toString().uppercase())
63 | val selectedCategory =
64 | Categories.returnAsEnum(categoriesSpinner.selectedItem.toString().uppercase())
65 | if (selectedCategory==Categories.None && selectedCountry==Countries.None && topics.isEmpty()) {
66 | selectedCountry = Countries.US
67 | }
68 |
69 | val request = APIBuilder.Builder(BuildConfig.API_Topics_Top_Headlines)
70 | .setCountry(selectedCountry)
71 | .setCategory(selectedCategory)
72 | .setQuery(topics)
73 | .build()
74 | .buildUrl()
75 | // val sharedPreferences: SharedPreferences = getSharedPreferences("MyPrefs", MODE_PRIVATE)
76 | // val editor = sharedPreferences.edit()
77 | // editor.putString("query", queryUrl)
78 | // editor.apply()
79 | startActivity(Intent(this, MainActivity::class.java).putExtra("request", request))
80 | }
81 | }
82 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/whatnow/core/ui/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.whatnow.core.ui
2 |
3 | import android.os.Bundle
4 | import android.widget.ImageButton
5 | import androidx.activity.enableEdgeToEdge
6 | import androidx.appcompat.app.AppCompatActivity
7 | import androidx.core.view.ViewCompat
8 | import androidx.core.view.WindowInsetsCompat
9 | import androidx.fragment.app.Fragment
10 | import com.example.whatnow.BuildConfig
11 | import com.example.whatnow.R
12 | import com.example.whatnow.core.api.APIBuilder
13 | import com.example.whatnow.core.data.Countries
14 | import com.example.whatnow.core.api.DefaultRetrofitFactory
15 | import com.example.whatnow.core.data.Languages
16 | import com.example.whatnow.news.ui.SearchFragment
17 | import com.example.whatnow.databinding.ActivityMainBinding
18 | import com.example.whatnow.news.data.NewsCallable
19 | import com.example.whatnow.news.data.NewsManager
20 | import com.example.whatnow.news.ui.NoNewsFragment
21 |
22 | class MainActivity : AppCompatActivity(), SearchFragment.OnSearchListener {
23 | private lateinit var binding: ActivityMainBinding
24 | private lateinit var newsManager: NewsManager
25 | override fun onCreate(savedInstanceState: Bundle?) {
26 | super.onCreate(savedInstanceState)
27 | enableEdgeToEdge()
28 | binding = ActivityMainBinding.inflate(layoutInflater)
29 | setContentView(binding.root)
30 | ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
31 | val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
32 | v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
33 | insets
34 | }
35 |
36 | val retrofit = DefaultRetrofitFactory().createRetrofit()
37 | val newsCallable = retrofit.create(NewsCallable::class.java)
38 | newsManager = NewsManager(this, binding, newsCallable)
39 |
40 | //
41 | // val query =
42 | // if (getSharedPreferences("UserPrefs", MODE_PRIVATE).getString("query", "") != "") {
43 | // getSharedPreferences("UserPrefs", MODE_PRIVATE).getString("query", "")!!
44 | // } else APIBuilder.Builder(BuildConfig.API_Topics_Top_Headlines)
45 | // .setCountry(Countries.US)
46 | // .build()
47 | // .buildUrl()
48 | val query = intent.getStringExtra("request") ?: APIBuilder.Builder(BuildConfig.API_Topics_Top_Headlines)
49 | .setCountry(Countries.US)
50 | .build()
51 | .buildUrl()
52 | newsManager.loadNews(query)
53 | binding.swipeRefresh.setOnRefreshListener { newsManager.loadNews(query) }
54 |
55 | if (intent.getBooleanExtra("openSearchFragment", false)) {
56 | openSearchFragment()
57 | }
58 | val openSearchBtn: ImageButton = findViewById(R.id.search_ib)
59 | openSearchBtn.setOnClickListener {
60 | openSearchFragment()
61 | }
62 |
63 | }
64 |
65 | private fun openSearchFragment() {
66 | val searchFragment = SearchFragment()
67 | supportFragmentManager.beginTransaction()
68 | .setCustomAnimations(
69 | R.anim.enter_from_right,
70 | R.anim.exit_to_left,
71 | R.anim.enter_from_right,
72 | R.anim.exit_to_left
73 | )
74 | .replace(R.id.fragment_container, searchFragment)
75 | .addToBackStack(null)
76 | .commit()
77 | }
78 |
79 | fun showNoNewsFragment() {
80 | replaceFragment(NoNewsFragment())
81 | }
82 |
83 | private fun replaceFragment(fragment: Fragment) {
84 | supportFragmentManager.beginTransaction()
85 | .replace(R.id.fragment_container, fragment)
86 | .addToBackStack(null)
87 | .commit()
88 | }
89 |
90 | override fun onSearch(query: String, language: String, fromDate: String, toDate: String) {
91 | val selectedLanguage = Languages.returnAsEnum(language)
92 | newsManager.performSearch(query, language = selectedLanguage, from = fromDate, to = toDate)
93 | }
94 |
95 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/whatnow/setteings/ui/SettingsActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.whatnow.setteings.ui
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import android.content.res.Configuration
6 | import android.os.Bundle
7 | import androidx.activity.enableEdgeToEdge
8 | import androidx.appcompat.app.AppCompatActivity
9 | import androidx.appcompat.app.AppCompatDelegate
10 | import androidx.appcompat.widget.SwitchCompat
11 | import androidx.appcompat.widget.Toolbar
12 | import androidx.core.view.ViewCompat
13 | import androidx.core.view.WindowInsetsCompat
14 | import com.example.whatnow.R
15 | import java.util.Locale
16 |
17 | class SettingsActivity : AppCompatActivity() {
18 | private lateinit var modeSwitch: SwitchCompat
19 | private var nightMode:Boolean=false
20 | private var editor: SharedPreferences.Editor?=null
21 | private var sharedPreferences:SharedPreferences?=null
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | super.onCreate(savedInstanceState)
24 | enableEdgeToEdge()
25 | setContentView(R.layout.activity_settings)
26 | ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
27 | val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
28 | v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
29 | insets
30 | }
31 | val switchLanguage: SwitchCompat = findViewById(R.id.switch_language)
32 | val sharedPref = getSharedPreferences("app_preferences", Context.MODE_PRIVATE)
33 | val savedLanguage = sharedPref.getString("language", "en") ?: "en"
34 |
35 | // Set switch state based on current locale
36 | switchLanguage.isChecked = savedLanguage == "ar"
37 |
38 | switchLanguage.setOnCheckedChangeListener { _, isChecked ->
39 | val language = if (isChecked) "ar" else "en"
40 | val editor = sharedPref.edit()
41 | editor.putString("language", language)
42 | editor.apply()
43 | recreate()
44 | }
45 | modeSwitch=findViewById(R.id.switch_theme)
46 | sharedPreferences=getSharedPreferences("MODE", Context.MODE_PRIVATE)
47 | nightMode=sharedPreferences?.getBoolean("night",false)!!
48 |
49 | if (nightMode){
50 | modeSwitch.isChecked=true
51 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
52 | }
53 | modeSwitch.setOnCheckedChangeListener{compoundButton,state->
54 | if (nightMode){
55 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
56 | editor=sharedPreferences?.edit()
57 | editor?.putBoolean("night",false)
58 | }else{
59 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
60 | editor=sharedPreferences?.edit()
61 | editor?.putBoolean("night",true)
62 |
63 |
64 | }
65 | editor?.apply()
66 |
67 | val toolbar: Toolbar = findViewById(R.id.toolbar)
68 | setSupportActionBar(toolbar)
69 |
70 |
71 | supportActionBar?.title = "Setting"
72 |
73 |
74 | supportActionBar?.setDisplayHomeAsUpEnabled(true)
75 |
76 |
77 | toolbar.setNavigationOnClickListener {
78 | onBackPressed()
79 | }
80 |
81 |
82 |
83 | }
84 |
85 | }
86 | private fun setLocale(languageCode: String) {
87 | val locale = Locale(languageCode)
88 | Locale.setDefault(locale)
89 | val config = Configuration()
90 | config.locale = locale
91 | resources.updateConfiguration(config, resources.displayMetrics)
92 |
93 | // Save language preference
94 | val sharedPref = getSharedPreferences("app_preferences", Context.MODE_PRIVATE)
95 | with(sharedPref.edit()) {
96 | putString("language", languageCode)
97 | apply()
98 | }
99 |
100 | // Restart the activity to apply changes
101 | val intent = intent
102 | finish()
103 | startActivity(intent)
104 | }
105 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | What Now!
3 | https://lottie.host/f5bcb3a8-e3d7-48b3-9e2c-568d4171825f/eMH5BHxtpX.json
4 |
5 | - Select a country
6 | - Argentina
7 | - Australia
8 | - Austria
9 | - Belgium
10 | - Brazil
11 | - Canada
12 | - China
13 | - Egypt
14 | - Germany
15 | - Hungary
16 | - Italy
17 | - Japan
18 | - Malaysia
19 | - Morocco
20 | - Norway
21 | - Poland
22 | - Russia
23 | - Saudi_Arabia
24 | - Singapore
25 | - Switzerland
26 | - Turkey
27 | - UAE
28 | - United Kingdom
29 | - US
30 |
31 |
32 | - Select a category
33 | - Business
34 | - Entertainment
35 | - General
36 | - Health
37 | - Science
38 | - Sports
39 | - Technology
40 |
41 |
42 | - Select a language
43 | - Arabic
44 | - German
45 | - English
46 | - Spanish
47 | - French
48 | - Italian
49 | - Dutch
50 | - Norwegian
51 | - Portuguese
52 | - Russian
53 | - Swedish
54 | - Urdu
55 | - Chinese
56 |
57 |
58 | Quick & Daily News
59 | Instant Notifications
60 |
61 | //Description
62 | Grow your Knowledge By our news app
63 |
64 | What Now
65 | Your News At Any Time
66 | news_Image
67 | ...
68 | share
69 | What are you interested in?
70 | • Categories
71 | • Country
72 | • Topics
73 | Enter a topic
74 | Submit
75 | Settings
76 | Dark Mode
77 | Language
78 | Email
79 | Password
80 | Forgot your password?
81 | Login
82 | Not a user?
83 | Confirm Password
84 | Sign Up
85 | Already a user?
86 | No News Found.
87 | Try Another Search
88 | What are you looking for?
89 | Advanced Search options:
90 | Sort By:
91 | Sort by Date
92 | Relevant
93 | Popularity
94 | From:
95 | To:
96 | Find!
97 | Select From Date
98 | Select to Date
99 |
100 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/whatnow/core/api/APIBuilder.kt:
--------------------------------------------------------------------------------
1 |
2 | package com.example.whatnow.core.api
3 |
4 | import com.example.whatnow.BuildConfig
5 | import com.example.whatnow.core.data.Categories
6 | import com.example.whatnow.core.data.Countries
7 | import com.example.whatnow.core.data.Languages
8 | import com.example.whatnow.core.data.SortBy
9 | import java.net.URLEncoder
10 | import java.nio.charset.StandardCharsets
11 |
12 | class APIBuilder private constructor(
13 | private val endpoint: String,
14 | private val query: String?,
15 | private val fromDate: String?,
16 | private val toDate: String?,
17 | private val language: Languages?,
18 | private val sortBy: SortBy?,
19 | private val country: Countries?,
20 | private val category: Categories?
21 | ) {
22 |
23 | class Builder(private val endpoint: String) {
24 | private var query: String? = null
25 | private var fromDate: String? = null
26 | private var toDate: String? = null
27 | private var language: Languages? = null
28 | private var sortBy: SortBy? = null
29 | private var country: Countries? = null
30 | private var category: Categories? = null
31 |
32 | fun setQuery(query: String) = apply { this.query = query }
33 | fun setFromDate(fromDate: String) = apply { this.fromDate = fromDate }
34 | fun setToDate(toDate: String) = apply { this.toDate = toDate }
35 | fun setLanguage(language: Languages) = apply { this.language = language }
36 | fun setSortBy(sortBy: SortBy) = apply { this.sortBy = sortBy }
37 | fun setCountry(country: Countries) = apply { this.country = country }
38 | fun setCategory(category: Categories) = apply { this.category = category }
39 |
40 | fun build(): APIBuilder {
41 | return APIBuilder(
42 | endpoint,
43 | query,
44 | fromDate,
45 | toDate,
46 | language,
47 | sortBy,
48 | country,
49 | category
50 | )
51 | }
52 | }
53 |
54 | fun buildUrl(): String {
55 | return StringBuilder().apply {
56 | append(baseChunk())
57 | append(endpoint)
58 | query?.let { append(addQuery(it)) }
59 | fromDate?.let { append(addFrom(it)) }
60 | toDate?.let { append(addTo(it)) }
61 | language?.let { append(addLanguage(it)) }
62 | sortBy?.let { append(addSortBy(it)) }
63 | country?.let { append(addCountry(it)) }
64 | category?.let { append(addCategory(it)) }
65 | append(addAPIKey())
66 | }.toString()
67 | }
68 |
69 | private fun addCategory(category: Categories): String {
70 | if (category != Categories.None) {
71 | return BuildConfig.API_Category + category.code + "&"
72 | }
73 | return ""
74 | }
75 |
76 | private fun addSortBy(sortBy: SortBy = SortBy.PUBLISHED_AT): String {
77 | if (sortBy != SortBy.None) {
78 | return BuildConfig.API_SortBy + sortBy.value + "&"
79 | }
80 | return ""
81 | }
82 |
83 | private fun addLanguage(language: Languages): String {
84 | if (language != Languages.None) {
85 | return BuildConfig.API_Language + language.code + "&"
86 | }
87 | return ""
88 | }
89 |
90 | private fun addTo(to: String = ""): String {
91 | return to.takeIf { it.isNotEmpty() }?.let { BuildConfig.API_To + it + "&" } ?: ""
92 | }
93 |
94 | private fun addFrom(from: String = ""): String {
95 | return from.takeIf { it.isNotEmpty() }?.let { BuildConfig.API_From + it + "&" } ?: ""
96 | }
97 |
98 | private fun addQuery(query: String): String {
99 | return query.takeIf { it.isNotEmpty() }
100 | ?.let { BuildConfig.API_q + buildSearchQuery(it) + "&" } ?: ""
101 | }
102 |
103 | private fun addCountry(country: Countries): String {
104 | return if (country != Countries.None) {
105 | BuildConfig.API_Country + country.code + "&"
106 | } else {
107 | ""
108 | }
109 | }
110 |
111 | private fun baseChunk(): String {
112 | return BuildConfig.API_baseUrl + BuildConfig.API_Version
113 | }
114 |
115 | private fun buildSearchQuery(q: String): String {
116 | return URLEncoder.encode(q, StandardCharsets.UTF_8.toString())
117 | }
118 |
119 | private fun addAPIKey(): String {
120 | return BuildConfig.API_KEY
121 | }
122 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # What Now! - News App
2 |
3 | > A comprehensive mobile news application that provides the latest updates on news from various domains, using a clean and modern UI.
4 |
5 | ---
6 |
7 | ## Table of Contents
8 | - [About](#about)
9 | - [Features](#features)
10 | - [Technologies](#technologies)
11 | - [Architecture](#architecture)
12 | - [Installation](#installation)
13 | - [Usage](#usage)
14 | - [Contributing](#contributing)
15 | - [License](#license)
16 | - [Contact](#contact)
17 |
18 | ---
19 |
20 | ## About
21 |
22 | **What Now** is a mobile news application built with Kotlin, utilizing **XML** for the UI. This app provides real-time updates from various news domains, including categories like politics, sports, technology, and more. It is designed with a focus on modular architecture, reusability, and scalability.
23 |
24 | ---
25 |
26 | ## Features
27 | - **Latest News Updates**: Browse the latest news based on categories like sports, politics, technology, and more.
28 | - **Advanced Search**: Search news by domain, language, date, and other filters.
29 | - **Clean UI**: Built with a user-friendly interface using modern Android UI principles.
30 | - **Multi-Language Support**: The app supports multiple languages based on the user's preference.
31 |
32 | ---
33 |
34 | ## Technologies
35 |
36 | - **Programming Language**: Kotlin
37 | - **Platform:** Android
38 | - **UI**: XML, View Binding, Lottie Animations, Material Design, Fragments
39 | - **Networking**: Retrofit
40 | - **Firebase**: Authentication, Realtime Database, Push Notifications
41 | - **Gradle Custom Plugins**: For build configuration and optimization
42 | - **Architecture:** MVVM
43 | - **Database:** Firestore
44 | - **State Management:** LiveData, ViewModel
45 | - **Local Storage:** SharedPreferences
46 |
47 | ---
48 |
49 | ## Architecture
50 |
51 | This project follows the **Multi-Modular Architecture** design with separation of concerns, leveraging best practices like:
52 |
53 | - **MVVM Pattern**: Model-View-ViewModel for managing UI-related data.
54 | - **Repository Pattern**: For abstracting the data layer and handling API calls.
55 | - **Builder Pattern**: For Building API calls.
56 | - **Factory Pattern**: Ritrofit
57 |
58 | ---
59 | ## Screenshots
60 | | Login | SettingAPI | Search For News |
61 | |:--------------------------------------------------------------------------------------:|:------------------------------------------------------------------------------------------------:|:--------------------------------------------------------------------------------------------------------:|
62 | |  |  |  |
63 |
64 | ---
65 |
66 | ## Installation
67 |
68 | 1. **Clone the repository:**
69 | ```bash
70 | git clone https://github.com/0xSA7/What-Now-NewsApp.git
71 | ```
72 | 2. **Open the project in Android Studio:**
73 | Open Android Studio.
74 | Select "Open an existing project".
75 | Navigate to the cloned repository and select it.
76 | 3. **Create a Firebase project and add the google-services.json file to the app directory.**
77 | 4. **This App uses API from https://newsapi.org/**
78 | 5. **Build the project:**
79 | Let Android Studio download and configure all dependencies.
80 | Once the build is complete, you can run the app on an emulator or a physical device.
81 |
82 | ---
83 |
84 | ## Contributing
85 |
86 | Contributions are welcome! Please follow these steps:
87 |
88 | 1. **Fork the Repository**
89 | 2. **Create a New Branch**
90 | ```bash
91 | git checkout -b feature/your-feature-name
92 | ```
93 | 3. **Commit Your Changes**
94 | ```bash
95 | git commit -m "Add some feature"
96 | ```
97 | 4. **Push to the Branch**
98 | ```bash
99 | git push origin feature/your-feature-name
100 | ```
101 | 5. **Create a Pull Request** explaining your changes.
102 | ---
103 |
104 | ## License
105 | This project is licensed under the MIT License - see the LICENSE file for details.
106 | ---
107 |
108 | ## Meet our team:
109 | - [Eyad Amro](https://github.com/eyadamr905)
110 | - [Khaled Mahmoud](https://github.com/KhaledMa7mouad)
111 | - [SA7](https://github.com/0xSA7)
112 | - [Polla joseph](https://github.com/PollaJoseph)
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
22 |
24 |
26 |
28 |
30 |
32 |
34 |
36 |
38 |
40 |
42 |
44 |
46 |
48 |
50 |
52 |
54 |
56 |
58 |
60 |
62 |
64 |
66 |
68 |
70 |
72 |
74 |
75 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
24 |
25 |
26 |
36 |
37 |
44 |
45 |
51 |
52 |
57 |
58 |
63 |
64 |
70 |
71 |
72 |
73 |
74 |
84 |
85 |
92 |
93 |
99 |
100 |
106 |
107 |
112 |
113 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_setting_api.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
26 |
27 |
28 |
41 |
42 |
50 |
51 |
52 |
65 |
66 |
74 |
75 |
76 |
89 |
90 |
105 |
106 |
107 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/whatnow/auth/SignupActivity.kt:
--------------------------------------------------------------------------------
1 | //package com.example.whatnow.auth
2 | //
3 | //import android.content.Intent
4 | //import android.os.Bundle
5 | //import android.widget.Toast
6 | //import androidx.activity.enableEdgeToEdge
7 | //import androidx.appcompat.app.AppCompatActivity
8 | //import androidx.core.view.ViewCompat
9 | //import androidx.core.view.WindowInsetsCompat
10 | //import com.example.whatnow.databinding.ActivitySignupBinding
11 | //import com.google.firebase.auth.FirebaseAuth
12 | //import com.google.firebase.auth.ktx.auth
13 | //import com.google.firebase.ktx.Firebase
14 | //
15 | //class SignupActivity : AppCompatActivity() {
16 | // private lateinit var binding: ActivitySignupBinding
17 | // private lateinit var auth: FirebaseAuth
18 | //
19 | // override fun onCreate(savedInstanceState: Bundle?) {
20 | // super.onCreate(savedInstanceState)
21 | // enableEdgeToEdge()
22 | //
23 | // // Initialize View Binding
24 | // binding = ActivitySignupBinding.inflate(layoutInflater)
25 | // setContentView(binding.root)
26 | //
27 | // // Apply Window Insets
28 | // ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets ->
29 | // val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
30 | // v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
31 | // insets
32 | // }
33 | //
34 | // // Initialize FirebaseAuth
35 | // auth = Firebase.auth
36 | //
37 | // binding.alreadyUserTv.setOnClickListener {
38 | // startActivity(Intent(this, SignInActivity::class.java))
39 | // finish()
40 | // }
41 | //
42 | // binding.signUpBtn.setOnClickListener {
43 | // val email = binding.emailEt.text.toString()
44 | // val pass = binding.passwordEt.text.toString()
45 | // val conPass = binding.conPassEt.text.toString()
46 | //
47 | // when {
48 | // email.isBlank() || pass.isBlank() || conPass.isBlank() ->
49 | // Toast.makeText(this, "Missing Field/s!", Toast.LENGTH_SHORT).show()
50 | // pass.length < 6 ->
51 | // Toast.makeText(this, "Short Password!", Toast.LENGTH_SHORT).show()
52 | // pass != conPass ->
53 | // Toast.makeText(this, "Passwords don't match", Toast.LENGTH_SHORT).show()
54 | // else -> {
55 | // /// binding.progress.isVisible = true
56 | // signUpUser(email, pass)
57 | // }
58 | // }
59 | // }
60 | // }
61 | //
62 | // private fun signUpUser(email: String, pass: String) {
63 | // auth.createUserWithEmailAndPassword(email, pass)
64 | // .addOnCompleteListener(this) { task ->
65 | // if (task.isSuccessful) {
66 | // verifyEmail()
67 | // } else {
68 | // val message = task.exception?.message ?: "Sign up failed"
69 | // Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
70 | // // binding.progress.isVisible = false
71 | // }
72 | // }
73 | // }
74 | //
75 | // private fun verifyEmail() {
76 | // auth.currentUser?.sendEmailVerification()
77 | // ?.addOnCompleteListener { task ->
78 | // if (task.isSuccessful) {
79 | // Toast.makeText(this, "Verification Email Sent!", Toast.LENGTH_SHORT).show()
80 | // } else {
81 | // Toast.makeText(this, "Failed to send verification email", Toast.LENGTH_SHORT).show()
82 | // }
83 | // //binding.progress.isVisible = false
84 | // }
85 | // }
86 | //}
87 |
88 | package com.example.whatnow.auth
89 |
90 | import android.content.Intent
91 | import android.os.Bundle
92 | import android.widget.Toast
93 | import androidx.activity.viewModels
94 | import androidx.appcompat.app.AppCompatActivity
95 | import androidx.core.view.ViewCompat
96 | import androidx.core.view.WindowInsetsCompat
97 | import com.example.whatnow.databinding.ActivitySignupBinding
98 |
99 | class SignupActivity : AppCompatActivity() {
100 |
101 | private lateinit var binding: ActivitySignupBinding
102 | private val viewModel: AuthViewModel by viewModels { AuthViewModelFactory(UserRepository()) }
103 |
104 | override fun onCreate(savedInstanceState: Bundle?) {
105 | super.onCreate(savedInstanceState)
106 | binding = ActivitySignupBinding.inflate(layoutInflater)
107 | setContentView(binding.root)
108 |
109 | // Apply Window Insets
110 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets ->
111 | val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
112 | v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
113 | insets
114 | }
115 |
116 | binding.alreadyUserTv.setOnClickListener {
117 | startActivity(Intent(this, SignInActivity::class.java))
118 | finish()
119 | }
120 |
121 | binding.signUpBtn.setOnClickListener {
122 | val email = binding.emailEt.text.toString()
123 | val pass = binding.passwordEt.text.toString()
124 | val conPass = binding.conPassEt.text.toString()
125 |
126 | when {
127 | email.isBlank() || pass.isBlank() || conPass.isBlank() ->
128 | Toast.makeText(this, "Missing Field/s!", Toast.LENGTH_SHORT).show()
129 | pass.length < 6 ->
130 | Toast.makeText(this, "Short Password!", Toast.LENGTH_SHORT).show()
131 | pass != conPass ->
132 | Toast.makeText(this, "Passwords don't match", Toast.LENGTH_SHORT).show()
133 | else -> {
134 | // binding.progress.isVisible = true
135 | viewModel.signUp(email, pass)
136 | }
137 | }
138 | }
139 |
140 | observeViewModel()
141 | }
142 |
143 | private fun observeViewModel() {
144 | viewModel.signUpResult.observe(this) { result ->
145 | result.onSuccess {
146 | Toast.makeText(this, "Verification Email Sent!", Toast.LENGTH_SHORT).show()
147 | // Navigate to another screen or perform any additional actions
148 | }.onFailure { exception ->
149 | Toast.makeText(this, exception.message, Toast.LENGTH_SHORT).show()
150 | // binding.progress.isVisible = false
151 | }
152 | }
153 | }
154 | }
155 |
156 |
157 |
158 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_search.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
25 |
26 |
30 |
31 |
32 |
33 |
44 |
45 |
46 |
55 |
56 |
57 |
65 |
66 |
71 |
72 |
77 |
78 |
83 |
84 |
85 |
86 |
94 |
95 |
96 |
105 |
106 |
116 |
117 |
123 |
124 |
125 |
134 |
135 |
145 |
146 |
152 |
153 |
154 |
164 |
165 |
166 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/whatnow/auth/SignInActivity.kt:
--------------------------------------------------------------------------------
1 | //package com.example.whatnow.auth
2 | //
3 | //import android.content.Context
4 | //import android.content.Intent
5 | //import android.content.SharedPreferences
6 | //import android.os.Bundle
7 | //import android.widget.Toast
8 | //import androidx.activity.enableEdgeToEdge
9 | //import androidx.appcompat.app.AppCompatActivity
10 | //import androidx.core.view.ViewCompat
11 | //import androidx.core.view.WindowInsetsCompat
12 | //import com.example.whatnow.R
13 | //import com.example.whatnow.setteings.data.SettingAPI
14 | //import com.example.whatnow.databinding.ActivitySignInBinding
15 | //import com.google.firebase.auth.FirebaseAuth
16 | //import com.google.firebase.auth.ktx.auth
17 | //import com.google.firebase.ktx.Firebase
18 | //
19 | //class SignInActivity : AppCompatActivity() {
20 | // private lateinit var binding: ActivitySignInBinding
21 | // private lateinit var auth: FirebaseAuth
22 | // private lateinit var sharedPreferences: SharedPreferences
23 | //
24 | // override fun onCreate(savedInstanceState: Bundle?) {
25 | // super.onCreate(savedInstanceState)
26 | // enableEdgeToEdge()
27 | // binding = ActivitySignInBinding.inflate(layoutInflater)
28 | // setContentView(binding.root)
29 | //
30 | // ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
31 | // val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
32 | // v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
33 | // insets
34 | // }
35 | //
36 | // // Initialize Firebase Auth
37 | // auth = Firebase.auth
38 | // // Initialize sharedPreferences
39 | // sharedPreferences = getSharedPreferences("UserPrefs", Context.MODE_PRIVATE)
40 | //
41 | // loaduserdata()
42 | //
43 | // // Sign-up redirection
44 | // binding.notUserTv.setOnClickListener {
45 | // startActivity(Intent(this, SignupActivity::class.java))
46 | // finish()
47 | // }
48 | //
49 | // // Login Button Logic
50 | // binding.loginBtn.setOnClickListener {
51 | // val email = binding.emailEt.text.toString()
52 | // val password = binding.passwordEt.text.toString()
53 | // if (email.isBlank() || password.isBlank()) {
54 | // Toast.makeText(this, "Missing Field/s!", Toast.LENGTH_SHORT).show()
55 | // } else {
56 | // // binding.progress.isVisible = true
57 | // login(email, password)
58 | // }
59 | // }
60 | //
61 | // // Forgot Password Logic
62 | // binding.forgotPassTv.setOnClickListener {
63 | // val email = binding.emailEt.text.toString()
64 | // if (email.isBlank()) {
65 | // binding.emailEt.error = "Required!"
66 | // } else {
67 | // sendPasswordResetEmail(email)
68 | // }
69 | // }
70 | // }
71 | //
72 | // private fun sendPasswordResetEmail(email: String) {
73 | // auth.sendPasswordResetEmail(email)
74 | // .addOnCompleteListener { task ->
75 | // if (task.isSuccessful) {
76 | // Toast.makeText(this, "Password reset email sent!", Toast.LENGTH_SHORT).show()
77 | // } else {
78 | // val errorMessage = task.exception?.message ?: "Error occurred!"
79 | // Toast.makeText(this, errorMessage, Toast.LENGTH_SHORT).show()
80 | // }
81 | // }
82 | // }
83 | //
84 | // private fun login(email: String, password: String) {
85 | // auth.signInWithEmailAndPassword(email, password)
86 | // .addOnCompleteListener(this) { task ->
87 | // if (task.isSuccessful) {
88 | // // binding.progress.isVisible = false
89 | // if (auth.currentUser!!.isEmailVerified) {
90 | // saveuserdata(email, password)
91 | // startActivity(Intent(this, SettingAPI::class.java))
92 | // finish()
93 | // } else {
94 | // Toast.makeText(this, "Please verify your email first", Toast.LENGTH_SHORT)
95 | // .show()
96 | // }
97 | // } else {
98 | // val message = task.exception!!.message
99 | // Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
100 | // // binding.progress.isVisible = false
101 | // }
102 | // }
103 | // }
104 | //
105 | // private fun saveuserdata(email: String, password: String) {
106 | // val editor = sharedPreferences.edit()
107 | // editor.putString("email", email)
108 | // editor.putString("password", password)
109 | // editor.apply()
110 | //
111 | // }
112 | //
113 | //
114 | // private fun loaduserdata() {
115 | //
116 | // val savedEmail = sharedPreferences.getString("email", "")
117 | // val savedPassword = sharedPreferences.getString("password", "")
118 | //
119 | // if (!savedEmail!!.isBlank() && !savedPassword!!.isBlank()) {
120 | //
121 | // binding.emailEt.setText(savedEmail)
122 | // binding.passwordEt.setText(savedPassword)
123 | // }
124 | //
125 | //
126 | // }
127 | //
128 | //
129 | // private fun checkEmailVerification() {
130 | // // This function can be used if needed to handle email verification logic
131 | // }
132 | //}
133 |
134 | package com.example.whatnow.auth
135 |
136 | import android.content.Context
137 | import android.content.Intent
138 | import android.content.SharedPreferences
139 | import android.os.Bundle
140 | import android.widget.Toast
141 | import androidx.activity.enableEdgeToEdge
142 | import androidx.activity.viewModels
143 | import androidx.appcompat.app.AppCompatActivity
144 | import androidx.core.view.ViewCompat
145 | import androidx.core.view.WindowInsetsCompat
146 | import com.example.whatnow.R
147 | import com.example.whatnow.setteings.data.SettingAPI
148 | import com.example.whatnow.databinding.ActivitySignInBinding
149 |
150 | class SignInActivity : AppCompatActivity() {
151 | private lateinit var binding: ActivitySignInBinding
152 | private lateinit var sharedPreferences: SharedPreferences
153 | private val viewModel: AuthViewModel by viewModels { AuthViewModelFactory(UserRepository()) }
154 |
155 | override fun onCreate(savedInstanceState: Bundle?) {
156 | super.onCreate(savedInstanceState)
157 | enableEdgeToEdge()
158 | binding = ActivitySignInBinding.inflate(layoutInflater)
159 | setContentView(binding.root)
160 |
161 | ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
162 | val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
163 | v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
164 | insets
165 | }
166 |
167 | // Initialize sharedPreferences
168 | sharedPreferences = getSharedPreferences("UserPrefs", Context.MODE_PRIVATE)
169 |
170 | loadUserData()
171 |
172 | binding.notUserTv.setOnClickListener {
173 | startActivity(Intent(this, SignupActivity::class.java))
174 | finish()
175 | }
176 |
177 | binding.loginBtn.setOnClickListener {
178 | val email = binding.emailEt.text.toString()
179 | val password = binding.passwordEt.text.toString()
180 | if (email.isBlank() || password.isBlank()) {
181 | Toast.makeText(this, "Missing Field/s!", Toast.LENGTH_SHORT).show()
182 | } else {
183 | // binding.progress.isVisible = true
184 | viewModel.signIn(email, password)
185 | }
186 | }
187 |
188 | binding.forgotPassTv.setOnClickListener {
189 | val email = binding.emailEt.text.toString()
190 | if (email.isBlank()) {
191 | binding.emailEt.error = "Required!"
192 | } else {
193 | viewModel.sendPasswordResetEmail(email)
194 | }
195 | }
196 |
197 | observeViewModel()
198 | }
199 |
200 | private fun observeViewModel() {
201 | viewModel.signInResult.observe(this) { result ->
202 | result.onSuccess {
203 | saveUserData(binding.emailEt.text.toString(), binding.passwordEt.text.toString())
204 | startActivity(Intent(this, SettingAPI::class.java))
205 | finish()
206 | }.onFailure { exception ->
207 | Toast.makeText(this, exception.message, Toast.LENGTH_SHORT).show()
208 | // binding.progress.isVisible = false
209 | }
210 | }
211 |
212 | viewModel.passwordResetResult.observe(this) { result ->
213 | result.onSuccess {
214 | Toast.makeText(this, "Password reset email sent!", Toast.LENGTH_SHORT).show()
215 | }.onFailure { exception ->
216 | Toast.makeText(this, exception.message, Toast.LENGTH_SHORT).show()
217 | }
218 | }
219 | }
220 |
221 | private fun saveUserData(email: String, password: String) {
222 | val editor = sharedPreferences.edit()
223 | editor.putString("email", email)
224 | editor.putString("password", password)
225 | editor.apply()
226 | }
227 |
228 | private fun loadUserData() {
229 | val savedEmail = sharedPreferences.getString("email", "")
230 | val savedPassword = sharedPreferences.getString("password", "")
231 |
232 | if (!savedEmail.isNullOrBlank() && !savedPassword.isNullOrBlank()) {
233 | binding.emailEt.setText(savedEmail)
234 | binding.passwordEt.setText(savedPassword)
235 | }
236 | }
237 | }
238 |
239 |
240 |
241 |
242 |
--------------------------------------------------------------------------------